執行緒池的那些事其一

 

一,什麼是執行緒池?

   簡單來說,管理執行緒的池子。幫我們重複管理執行緒,避免創建大量的執行緒增加開銷。

二,為什麼用執行緒池?

  1. 降低資源消耗。通過重複利用已創建的執行緒降低執行緒創建和銷毀造成的消耗。

  2. 提高響應速度。當任務到達時,任務可以不需要的等到執行緒創建就能立即執行。

  3. 提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。

三,什麼時候用執行緒池?

  1. 單個任務處理時間比較短

  2. 需要處理的任務數量很大

四,解讀執行緒池的原理

  A,七個核心參數

  1,corePoolSize,核心執行緒數量

  默認情況下,在創建了執行緒池後,執行緒池中的執行緒數為0,當有任務來之後,就會創建一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中

  2,maximumPoolSize,執行緒數最大值

  3,keepLiveTime,多長時間被回收

  默認情況下,只有當執行緒池中的執行緒數大於corePoolSize時,keepAliveTime才會起作用,直到執行緒池中的執行緒數不大於corePoolSize,即當執行緒池中的執行緒數大於corePoolSize時,如果一個執行緒空閑的時間達到keepAliveTime,則會終止,直到執行緒池中的執行緒數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在執行緒池中的執行緒數不大於corePoolSize時,keepAliveTime參數也會起作用,直到執行緒池中的執行緒數為0;

  4,timeUnit,設置keepLiveTime的時間單位,共7種單位:d, h, m, s, ms, mis, ns 

  5,workQueue,存放提交至執行緒池但未被執行的任務

  ArrayBlockingQueue(有界隊列),是一個用數組實現的有界阻塞隊列,按FIFO排序量。

  LinkedBlockingQueue(可設置容量隊列),基於鏈表結構的阻塞隊列,按FIFO排序任務,容量可以選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQuene;newFixedThreadPool執行緒池使用了這個隊列

  DelayQueue(延遲隊列),是一個任務定時周期的延遲執行的隊列。根據指定的執行時間從小到大排序,否則根據插入到隊列的先後排序。newScheduledThreadPool執行緒池使用了這個隊列。

  PriorityBlockingQueue(優先順序隊列),是具有優先順序的無界阻塞隊列;

  SynchronousQueue(同步隊列),一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個執行緒調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene,newCachedThreadPool執行緒池使用了這個隊列。

  6,threadFactory,創建執行緒的工廠

  用於設置創建執行緒的工廠,可以通過執行緒工廠給每個創建出來的執行緒設置更有意義的名字。

使用開源框架guava提供的ThreadFactoryBuilder可以快速給執行緒池裡的執行緒設置有意義的名字,code: new ThreadFactoryBuilder().setNameFormat(“Hello-Engine”).build();

  7,rejectedExecutionHandler,拒絕策略設置

  CallerRunsPolicy:只要執行緒池沒關閉,就用調用者所在執行緒來運行任務

  AbortPolicy:直接拋RejectedExecutionException 異常

  DiscardPolicy:不處理,直接扔了

  DiscardOldestPolicy:把隊列里待最久的那個任務扔了,並執行當前任務

  支援實現自己的 RejectedExecutionHandler 介面自定義策略,如記錄日誌類。

  B,執行緒池的狀態

RUNNING

  • 該狀態的執行緒池會接收新任務,並處理阻塞隊列中的任務;

  • 調用執行緒池的shutdown()方法,可以切換到SHUTDOWN狀態;

  • 調用執行緒池的shutdownNow()方法,可以切換到STOP狀態;

SHUTDOWN

  • 該狀態的執行緒池不會接收新任務,但會處理阻塞隊列中的任務;

  • 隊列為空,並且執行緒池中執行的任務也為空,進入TIDYING狀態;

STOP

  • 該狀態的執行緒不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;

  • 執行緒池中執行的任務為空,進入TIDYING狀態;

TIDYING

  • 該狀態表明所有的任務已經運行終止,記錄的任務數量為0。

  • terminated()執行完畢,進入TERMINATED狀態

TERMINATED

  • 該狀態表示執行緒池徹底終止

 

  C,任務執行流程

  1,execute()方法

  a,主要流程圖如下:

  b,ThreadPoolExecutor執行流程

  • 提交一個任務,執行緒池裡存活的核心執行緒數小於執行緒數corePoolSize時,執行緒池會創建一個核心執行緒去處理提交的任務。

  • 如果執行緒池核心執行緒數已滿,即執行緒數已經等於corePoolSize,一個新提交的任務,會被放進任務隊列workQueue排隊等待執行。

  • 當執行緒池裡面存活的執行緒數已經等於corePoolSize了,並且任務隊列workQueue也滿,判斷執行緒數是否達到maximumPoolSize,即最大執行緒數是否已滿,如果沒到達,創建一個非核心執行緒執行提交的任務。

  • 如果當前的執行緒數達到了maximumPoolSize,還有新的任務過來的話,直接採用拒絕策略處理。

 

   c,源碼解讀

 2,submit()方法

ps: 執行緒池拋異常了,如何處理?

 

五,怎樣使用執行緒池?

A,Executors常用方法及問題

 1,常用方法四種:

newFixedThreadPool 創建一個定長執行緒池,可控制執行緒最大並發數,超出的執行緒會在隊列中等待。

 

工作機制:

 

適用場景:用於處理CPU密集型的任務,確保CPU在長期被工作執行緒使用的情況下,儘可能的少的分配執行緒,即適用執行長期的任務。

  •  

newSingleThreadExecutor 創建一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

 

工作機制:

 

適用場景:用於串列執行任務的場景,一個任務一個任務地執行。

  •  

newCachedThreadPool創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒。

 

工作機制:

       

 

適用場景:用於並發執行大量短期的小任務。

  •  

newScheduledThreadPool 創建一個定長執行緒池,支援定時及周期性任務執行。

 

工作機制:

 

  • 適用場景: 周期性執行任務的場景,需要限制執行緒數量的場景

 

 2,使用Executors的問題, 來自阿里Java規範的建議:

       

B,自定義執行緒池

1,合理配置執行緒池

     通常我們是需要根據這批任務執行的性質來確定的。

  • IO 密集型任務:由於執行緒並不是一直在運行,所以可以儘可能的多配置執行緒,比如 CPU 個數 * 2

  • CPU 密集型任務(大量複雜的運算)應當分配較少的執行緒,比如 CPU 個數相當的大小。

     當然這些都是經驗值,最好的方式還是根據實際情況測試得出最佳配置。

2,推薦使用Guava提供的ThreadFactoryBuilder來創建執行緒池
     

 

 

 

 

Tags: