1. 程式人生 > >自己對執行緒池的理解

自己對執行緒池的理解

     工作中難免會遇到需要使用執行緒的地方,在使用執行緒的過程中,執行緒池我覺得是非常有必要去簡單瞭解一下的;這裡我會將自己所體會的感悟的一些基本內容分享出來,希望能對少數人有所幫助;如果有不對的地方,還請各位幫忙指出。

為什麼使用執行緒池?

     在Java中,如果每當一個請求到達就建立一個新執行緒,開銷是相當大的。在實際使用中,每個請求建立新執行緒的伺服器在建立和銷燬執行緒上花費的時間和消耗的系統資源,甚至可能要比花在實際處理實際的使用者請求的時間和資源要多的多。      執行緒池主要用來解決執行緒生命週期開銷問題和資源不足問題,通過對多個任務重用執行緒,執行緒建立的開銷被分攤到多個任務上了,而且由於在請求到達時執行緒已經存在,所以消除了建立所帶來的延遲。這樣,就可以立即請求服務,使應用程式響應更快。另外,通過適當的調整執行緒池中的執行緒資料可以防止出現資源不足的情況。

ThreadPoolExecutor類

  1. 根據corePoolSizemaxiMumPoolSize設定的邊界自動調整池大小
  2. 當新任務在execute(Runnable)中提交時,如果執行的執行緒小於corePoolSize,則建立新的執行緒來處理請求,即使其他輔助執行緒時空閒的。
  3. 當執行的執行緒大於corePoolSize而小於maximumPoolSize,則僅當佇列滿時才建立新執行緒。
  4. 如果設定的corePoolSizemaximumPoolSize相同,則建立了固定大小的執行緒池。
1.它所生成的一些常用的執行緒池:

      ThreadPoolExecutor是Executors類的實現,Executors類裡面提供了一些靜態工廠,生成一些常用的執行緒池:       newSingleThreadPool:

     建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。此執行緒池保證所有任務的執行順序按照任務的提交順序執行。 在這裡插入圖片描述       newFixedThreadPool:      建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒。 在這裡插入圖片描述       newCachedThreadPool:       建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,那麼就會回收部分空閒(60秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。 在這裡插入圖片描述
     在實際專案中,我一般使用的是固定的執行緒池;

2.四種處理策略:
  1. CallerRunsPolicy: 執行緒呼叫執行該任務的execute本身,池中沒有資源,則呼叫execute本身,能夠減緩新任務的提交速度; 在這裡插入圖片描述
  2. AbortPolicy: 處理程式遭到拒絕就丟擲異常,直接丟棄任務; 在這裡插入圖片描述
  3. DiscardPolicy: 與AbortPolicy幾乎一樣,只不過這種策略不會丟擲異常,直接丟棄任務; 在這裡插入圖片描述
  4. DiscarOldestPolicy: 如果執行程式尚未關閉,則處於工作佇列頭部的任務將被刪除,然後重新執行程式(如果再失敗,就繼續重複此過程) 在這裡插入圖片描述
3.BlockingQueue:

在這裡插入圖片描述

4.有界佇列與無界佇列:
  • 有界佇列: 不指定任務佇列的個數 new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()); 如果請求任務源源不斷的過來,而任務處理的時間變長,那麼在任務佇列中就會堵塞大量的請求,而一般來說這些請求都是有超時時間設定的;假設請求是通過套接字過來,當我們的後端處理程序處理完一個請求後,從佇列中拿下一個任務。
  • 無界佇列: 指定任務佇列的個數 new ThreadPoolExecutor(2, 4,60,TimeUnit.SECONDS, queue,new DaemonThreadFactory(poolName), new ThreadPoolExecutor.AbortPolicy()); corePoolSize 為2; maximumPoolSize 為4; 任務佇列大小 為2; 這裡會根據有界佇列時候執行緒池的執行順序,當新任務在方法execute中提交時,如果執行的執行緒小於corePoolSize,則建立新執行緒來處理請求。如果大於corePoolSize而小於maximumPoolSieze,則僅當佇列滿時才建立新執行緒。如果已經達到了maximumPoolSize,並且佇列已經滿了,就會拒絕繼續進來的請求; 所以,這裡不難看出;雖然這裡可能會使部分的請求任務失敗,但不至於會讓整個系統處於崩潰的地步。通過有界佇列,可以實現系統的過載保護,在高壓的情況下,我們系統的處理能力不會變為0,還能正常的對外服務。
5.keepAliveTime:
 ThreadPoolExecutor中的額定執行緒數是由corePoolSize決定的,當任務數量超過額定執行緒數,則將任務快取在BlockingQueue之中,當發現如果連queue也放不下了,就會再建立幾個執行緒,當建立執行緒後數量大於maximumPoolSize,接下來就會發生兩種狀況:
  • 任務不過來------keepAliveTime 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。

  • 任務任然過來------RejectedxecutionHandler 根據設定的拒絕策略來執行。

實際運用

  • 生成一個我們需要的執行緒池:(一般為固定執行緒池,有界佇列,執行緒數看併發量) 在這裡插入圖片描述

  • 把任務放線上程中去執行:(一般建議使用java8的Lambda表示式) 在這裡插入圖片描述

  • 使用Future包裝執行緒任務的執行結果: 在這裡插入圖片描述

  • 遍歷結果集,拿到我們執行緒執行任務的結果: 在這裡插入圖片描述 (附上一個在Http呼叫外部介面常用的JSON轉物件的方法) List idList = JSON.parseObject((String) ids, new TypeReference<List>() {});

    將JSON字串轉換成任何自己想要的型別;