Java執行緒池ThreadPoolExecutor初略探索
阿新 • • 發佈:2019-11-06
在作業系統中,執行緒是一個非常重要的資源,頻繁建立和銷燬大量執行緒會大大降低系統性能。Java執行緒池原理類似於資料庫連線池,目的就是幫助我們實現執行緒複用,減少頻繁建立和銷燬執行緒
ThreadPoolExecutor
ThreadPoolExecutor是執行緒池的核心類。首先看一下如何建立一個ThreadPoolExecutor。下面是ThreadPoolExecutor常用的一個構造方法:
構造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
使用案例
/** * 阻塞的執行緒池 */ private ThreadPoolExecutor executor = new ThreadPoolExecutor( 0, // corePoolSize:執行緒池維護執行緒的最少數量 4, // maximumPoolSize:執行緒池維護執行緒的最大數量 10000, // keepAliveTime:執行緒池維護執行緒所允許的空閒時間 TimeUnit.MILLISECONDS, // unite:執行緒池維護執行緒所允許的空閒時間的單位 new LinkedBlockingQueue<>(200), // workQueue:執行緒池所使用的緩衝佇列 new CallerBlockedPolicy() // handler:執行緒池對拒絕任務的處理策略,自定義拓展 );
構造方法引數說明
- corePoolSize:核心執行緒數量,當有新任務在
execute()
方法提交時,會執行以下判斷:- 如果執行的執行緒少於
corePoolSize
,則建立新執行緒來處理任務,即使執行緒池中的其他執行緒是空閒的; - 如果執行緒池中的執行緒數量大於等於
corePoolSize
且小於maximumPoolSize
,則只有當workQueue
滿時才建立新的執行緒去處理任務; - 如果設定的
corePoolSize
和maximumPoolSize
相同,則建立的執行緒池的大小是固定的,這時如果有新任務提交,若workQueue
未滿,則將請求放入workQueue
中,等待有空閒的執行緒去從workQueue
- 如果執行的執行緒數量大於等於maximumPoolSize,這時如果workQueue已經滿了,則通過handler所指定的策略來處理任務;
所以,任務提交時,判斷的順序為 corePoolSize –> workQueue –> maximumPoolSize。
- 如果執行的執行緒少於
- maximumPoolSize:最大執行緒數量;
- workQueue:等待佇列,當任務提交時,如果執行緒池中的執行緒數量大於等於
corePoolSize
的時候,把該任務封裝成一個Worker
物件放入等待佇列; - workQueue:儲存等待執行的任務的阻塞佇列,當提交一個新的任務到執行緒池以後, 執行緒池會根據當前執行緒池中正在執行著的執行緒的數量來決定對該任務的處理方式,主要有以下幾種處理方式:
- 直接切換:這種方式常用的佇列是
SynchronousQueue
,但現在還沒有研究過該佇列,這裡暫時還沒法介紹; - 使用無界佇列:一般使用基於連結串列的阻塞佇列
LinkedBlockingQueue
。如果使用這種方式,那麼執行緒池中能夠建立的最大執行緒數就是corePoolSize
,而maximumPoolSize
就不會起作用了(後面也會說到)。當執行緒池中所有的核心執行緒都是RUNNING狀態時,這時一個新的任務提交就會放入等待佇列中。 - 使用有界佇列:一般使用
ArrayBlockingQueue
。使用該方式可以將執行緒池的最大執行緒數量限制為maximumPoolSize
,這樣能夠降低資源的消耗,但同時這種方式也使得執行緒池對執行緒的排程變得更困難,因為執行緒池和佇列的容量都是有限的值,所以要想使執行緒池處理任務的吞吐率達到一個相對合理的範圍,又想使執行緒排程相對簡單,並且還要儘可能的降低執行緒池對資源的消耗,就需要合理的設定這兩個數量。- 如果要想降低系統資源的消耗(包括CPU的使用率,作業系統資源的消耗,上下文環境切換的開銷等), 可以設定較大的佇列容量和較小的執行緒池容量, 但這樣也會降低執行緒處理任務的吞吐量。
- 如果提交的任務經常發生阻塞,那麼可以考慮通過呼叫
setMaximumPoolSize()
方法來重新設定執行緒池的容量。 - 如果佇列的容量設定的較小,通常需要將執行緒池的容量設定大一點,這樣CPU的使用率會相對的高一些。但如果執行緒池的容量設定的過大,則在提交的任務數量太多的情況下,併發量會增加,那麼執行緒之間的排程就是一個要考慮的問題,因為這樣反而有可能降低處理任務的吞吐量。
- 直接切換:這種方式常用的佇列是
- keepAliveTime:執行緒池維護執行緒所允許的空閒時間。當執行緒池中的執行緒數量大於
corePoolSize
的時候,如果這時沒有新的任務提交,核心執行緒外的執行緒不會立即銷燬,而是會等待,直到等待的時間超過了keepAliveTime
; - threadFactory:它是ThreadFactory型別的變數,用來建立新執行緒。預設使用
Executors.defaultThreadFactory()
來建立執行緒。使用預設的- - - - ThreadFactory來建立執行緒時,會使新建立的執行緒具有相同的NORM_PRIORITY優先順序並且是非守護執行緒,同時也設定了執行緒的名稱。 - handler:它是
RejectedExecutionHandler
型別的變數,表示執行緒池的飽和策略。如果阻塞佇列滿了並且沒有空閒的執行緒,這時如果繼續提交任務,就需要採取一種策略處理該任務。執行緒池提供了4種策略:- AbortPolicy:直接丟擲異常,這是預設策略;
- CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;
- DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務;
- DiscardPolicy:直接丟棄任務;
執行緒池的監控
通過執行緒池提供的引數進行監控。執行緒池裡有一些屬性在監控執行緒池的時候可以使用
- getTaskCount:執行緒池已經執行的和未執行的任務總數;
- getCompletedTaskCount:執行緒池已完成的任務數量,該值小於等於taskCount;
- getLargestPoolSize:執行緒池曾經建立過的最大執行緒數量。通過這個資料可以知道執行緒池是否滿過,也就是達到了 maximumPoolSize;
- getPoolSize:執行緒池當前的執行緒數量;
- getActiveCount:當前執行緒池中正在執行任務的執行緒數量。
總結一下執行緒池新增任務的整個流程:
- 執行緒池剛剛建立是,執行緒數量為0;
- 執行execute新增新的任務時會線上程池建立一個新的執行緒;
- 當執行緒數量達到corePoolSize時,再新增新任務則會將任務放到workQueue佇列;
- 當佇列已滿放不下新的任務,再新增新任務則會繼續建立新執行緒,但執行緒數量不超過maximumPoolSize;
- 當執行緒數量達到maximumPoolSize時,再新增新任務則會丟擲異常。