多線程與併發-線程池-ThreadPoolExecutor
概述:
線程池這東西很常見,使用的原因更好的對線程進行管理,就是減少線程創建銷燬的開銷。
雖然有幾個現成的創建方法,但很多公司都不建議使用,而要求一定要通過ThreadPoolExecutor來創建,明確運行規則,指定更合適的參數。
這是一個參數最全的重載方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
一共有7個參數
corePoolSize,核心線程數
當有任務進來的時候,如果當前線程數未達到corePoolSize個數,則創建核心線程。
特點:
- 當線程數未達到最大值的時候,新任務進來,即時有空閒線程也不會複用,仍然創建新的線程。
- 核心線程一般不會被銷燬,即使處於空閒狀態,但是可以通過allowCoreThreadTimeOut(boolean value) 設置爲 true ,這時核心線程的空閒時間超過keepAliveTime設定的時間時會被銷燬
maximumPoolSize,最大線程池大小
線程池的最大的大小。
當核心線程已滿,並且阻塞隊列已滿時,將判斷當前線程數量是否達到最大值,如果未達到則創建一個非核心線程加入線程池,當線程數量>corePoolSize的時候,如果有線程的空閒時間超過keepAliveTime的時候將被銷燬
keepAliveTime,超時時間
就是上述兩個參數中提到的空閒時間,也就是線程最大空閒時間,默認用於非核心線程,通過 allowCoreThreadTimeOut(boolean value) 方法設置後,也會用於核心線程。(當然線程池中並沒有對線程進行標記是否爲核心線程,當數量超過核心線程數時,任意線程只有空閒時間超過設定值就會被銷燬)。
當設置爲0時,直接銷燬非核心線程
unit,時間單位
這個參數與keepAliveTime是一起的,設定的就是超時時間的時間單位,秒、分、時等。
workQueue,阻塞隊列
當核心線程池沒有空閒時,將把任務任務加入阻塞隊列。這個參數對線程池執行策略有很大影響。
有界隊列:隊列長度有上限,當核心線程已滿時,新任務加入阻塞隊列,阻塞隊列已滿時將根據maximumPoolSize判斷是否創建新的線程加入線程池,當達到最大值時,將根據拒絕策略處理後續的任務。
無界隊列:隊列沒有上限,也就是意味着隊列永遠不會達到上限,也就是說永遠不會創建非核心線程,任務的處理速度有限。同時新來任務可以無限的向隊列中添加。(警惕任務隊列無限堆積的風險)
名稱 | 是否有界 | 結構 | 默認值長度 | 最大值 | 備註 |
---|---|---|---|---|---|
ArrayBlockingQueue | 有界 | 數組 | 初始化指定大小 | ||
LinkedBlockingQueue | 有界 | 鏈表 | Integer.MAX_VALUE | Integer.MAX_VALUE | 出隊和入隊使用兩把鎖來實現 |
PriorityBlockingQueue | 無界 | 數組,堆 | 11 | Integer.MAX_VALUE-8 | 支持優先級但不能保證同優先級元素的順序 |
DelayQueue | 無界 | PriorityQueue | 11 | 創建元素時可以指定多久才能從隊列中獲取當前元素 | |
LinkedTransferQueue | 無界 | 鏈表 | CAS實現 | ||
LinkedBlockingDeque | 無界 | 鏈表 | 雙向阻塞隊列 | ||
SynchronousQueue | 隊列(公平),棧(非公平) | 0 | 內部沒有任何存放元素的能力 | CAS實現,對本人這有點像生成者消費者中鎖的通信 |
threadFactory,線程工廠
指定線程池中線程的創建方式,用於實現生成線程的方式、定義線程名格式、是否後臺執行等等,可以用 Executors.defaultThreadFactory() 默認的實現即可,也可以用 Guava 等三方庫提供的方法實現,如果有特殊要求的話可以自己定義。它最重要的地方應該就是定義線程名稱的格式,便於排查問題了吧。(可以把業務請求編號等信息作爲線程名稱)。
handler,拒絕策略
當沒有空閒的線程(線程池達到最大值maximumPoolSize)處理任務,並且等待隊列已滿(當然這隻對有界隊列有效),再有新任務進來的話,就要做一些取捨了,而這個參數就是指定取捨策略的,有下面四種策略可以選擇:
ThreadPoolExecutor.AbortPolicy//直接拋出異常,這是默認策略;
ThreadPoolExecutor.DiscardPolicy//直接丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy//丟棄隊列最前面的任務,然後將新來的任務加入等待隊列
ThreadPoolExecutor.CallerRunsPolicy//由線程池所在的線程處理該任務,比如在 main 函數中創建線程池,如果執行此策略,將有 main 線程來執行該任務
現有方法創建線程池(不提倡):
雖然並不提倡用 Executors 中的方法來創建線程池,但還是用他們來講一下幾種線程池的原理。
先看一下一部分源碼(有部分重載方法沒有展示,有threadFactory的參數重載方法):
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newScheduledThreadPool
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,週期性任務調度的類實現。關於週期調度的暫不做展開。
以上幾種總結一下:
方法 | corePoolSize | maximumPoolSize | keepAliveTime | unit | workQueue | threadFactory | handler |
---|---|---|---|---|---|---|---|
newFixedThreadPool | nThreads | nThreads | 0L | TimeUnit.MILLISECONDS | LinkedBlockingQueue | threadFactory | |
newSingleThreadExecutor | 1 | 1 | 0L | TimeUnit.MILLISECONDS | LinkedBlockingQueue | threadFactory | |
newCachedThreadPool | 0 | Integer.MAX_VALUE | 60L | TimeUnit.SECONDS | SynchronousQueue | threadFactory |
以上由ThreadPoolExecutor實現,只是參數有所不同。
不推薦使用的原因:
- newFixedThreadPool
由於使用默認使用的是 LinkedBlockingQueue 作爲等待隊列,這是一個無界隊列,這也是使用它的風險所在,除非你能保證提交的任務不會無節制的增長,否則不要使用無界隊列,這樣有可能造成等待隊列無限增加,造成 OOM。 - newSingleThreadExecutor
與newFixedThreadPool類似,只是設定了線程池大小爲1,理由同上。 - newCachedThreadPool
關鍵點就在於它使用 SynchronousQueue 作爲等待隊列,它不會保留任務,新任務進來後,直接創建臨時線程處理,這樣一來,也就容易造成無限制的創建線程,造成 OOM - newScheduledThreadPool
計劃型線程池,可以設置固定時間的延時或者定期執行任務,同樣是看線程池中有沒有空閒線程,如果有,直接拿來使用,如果沒有,則新建線程加入池。使用的是 DelayedWorkQueue 作爲等待隊列,這中類型的隊列會保證只有到了指定的延時時間,纔會執行任務