執行緒池的那些事其一
一,什麼是執行緒池?
簡單來說,管理執行緒的池子。幫我們重複管理執行緒,避免創建大量的執行緒增加開銷。
二,為什麼用執行緒池?
-
降低資源消耗。通過重複利用已創建的執行緒降低執行緒創建和銷毀造成的消耗。
-
提高響應速度。當任務到達時,任務可以不需要的等到執行緒創建就能立即執行。
-
提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。
三,什麼時候用執行緒池?
-
單個任務處理時間比較短
-
需要處理的任務數量很大
四,解讀執行緒池的原理
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來創建執行緒池