【轉載】硬核乾貨:4W字從源碼上分析JUC線程池ThreadPoolExecutor的實現原理
前提
很早之前就打算看一次JUC線程池ThreadPoolExecutor
的源碼實現,由於近段時間比較忙,一直沒有時間整理出源碼分析的文章。之前在分析擴展線程池實現可回調的Future
時候曾經提到併發大師Doug Lea
在設計線程池ThreadPoolExecutor
的提交任務的頂層接口Executor
只有一個無狀態的執行方法:
public interface Executor { |
而ExecutorService
提供了很多擴展方法底層基本上是基於Executor#execute()
方法進行擴展。本文着重分析ThreadPoolExecutor#execute()
的實現,筆者會從實現原理、源碼實現等角度結合簡化例子進行詳細的分析。ThreadPoolExecutor
的源碼從JDK8
到JDK11
基本沒有變化,本文編寫的時候使用的是JDK11
。
ThreadPoolExecutor的原理
ThreadPoolExecutor
裏面使用到JUC同步器框架AbstractQueuedSynchronizer
(俗稱AQS
)、大量的位操作、CAS
操作。ThreadPoolExecutor
提供了固定活躍線程(核心線程)、額外的線程(線程池容量 - 核心線程數這部分額外創建的線程,下面稱爲非核心線程)、任務隊列以及拒絕策略這幾個重要的功能。
JUC同步器框架
ThreadPoolExecutor
裏面使用到JUC同步器框架,主要用於四個方面:
- 全局鎖
mainLock
成員屬性,是可重入鎖ReentrantLock
類型,主要是用於訪問工作線程Worker
集合和進行數據統計記錄時候的加鎖操作。 - 條件變量
termination
,Condition
類型,主要用於線程進行等待終結awaitTermination()
方法時的帶期限阻塞。 - 任務隊列
workQueue
,BlockingQueue<Runnable>
類型,任務隊列,用於存放待執行的任務。 - 工作線程,內部類
Worker
類型,是線程池中真正的工作線程對象。
關於AQS
筆者之前寫過一篇相關源碼分析的文章:JUC同步器框架AbstractQueuedSynchronizer源碼圖文分析。
核心線程
這裏先參考ThreadPoolExecutor
的實現並且進行簡化,實現一個只有核心線程的線程池,要求如下:
- 暫時不考慮任務執行異常情況下的處理。
- 任務隊列爲無界隊列。
- 線程池容量固定爲核心線程數量。
- 暫時不考慮拒絕策略。
public class CoreThreadPool implements Executor { |
某次運行結果如下:
Thread:Worker-0,value:0 |
設計此線程池的時候,核心線程是懶創建的,如果線程空閒的時候則阻塞在任務隊列的take()
方法,其實對於ThreadPoolExecutor
也是類似這樣實現,只是如果使用了keepAliveTime
並且允許核心線程超時(allowCoreThreadTimeOut
設置爲true
)則會使用BlockingQueue#poll(keepAliveTime)
進行輪詢代替永久阻塞。
其他附加功能
構建ThreadPoolExecutor
實例的時候,需要定義maximumPoolSize
(線程池最大線程數)和corePoolSize
(核心線程數)。當任務隊列是有界的阻塞隊列,核心線程滿負載,任務隊列已經滿的情況下,會嘗試創建額外的maximumPoolSize - corePoolSize
個線程去執行新提交的任務。當ThreadPoolExecutor
這裏實現的兩個主要附加功能是:
- 一定條件下會創建非核心線程去執行任務,非核心線程的回收週期(線程生命週期終結時刻)是
keepAliveTime
,線程生命週期終結的條件是:下一次通過任務隊列獲取任務的時候並且存活時間超過keepAliveTime
。 - 提供拒絕策略,也就是在覈心線程滿負載、任務隊列已滿、非核心線程滿負載的條件下會觸發拒絕策略。
源碼分析
先分析線程池的關鍵屬性,接着分析其狀態控制,最後重點分析ThreadPoolExecutor#execute()
方法。
關鍵屬性
public class ThreadPoolExecutor extends AbstractExecutorService { |
下面看參數列表最長的構造函數:
public ThreadPoolExecutor(int corePoolSize, |
可以自定義核心線程數、線程池容量(最大線程數)、空閒線程等待任務週期、任務隊列、線程工廠、拒絕策略。下面簡單分析一下每個參數的含義和作用:
- **
corePoolSize
**:int類型,核心線程數量。 - **
maximumPoolSize
**:int類型,最大線程數量,也就是線程池的容量。 - **
keepAliveTime
**:long類型,線程空閒等待時間,也和工作線程的生命週期有關,下文會分析。 - **
unit
**:TimeUnit
類型,keepAliveTime
參數的時間單位,實際上keepAliveTime
最終會轉化爲納秒。 - **
workQueue
**:BlockingQueue<Runnable>
類型,等待隊列或者叫任務隊列。 - **
threadFactory
**:ThreadFactory
類型,線程工廠,用於創建工作線程(包括核心線程和非核心線程),默認使用Executors.defaultThreadFactory()
作爲內建線程工廠實例,一般自定義線程工廠才能更好地跟蹤工作線程。 - **
handler
**:RejectedExecutionHandler
類型,線程池的拒絕執行處理器,更多時候稱爲拒絕策略,拒絕策略執行的時機是當阻塞隊列已滿、沒有空閒的線程(包括核心線程和非核心線程)並且繼續提交任務。提供了4種內建的拒絕策略實現:AbortPolicy
:直接拒絕策略,也就是不會執行任務,直接拋出RejectedExecutionException
,這是默認的拒絕策略。DiscardPolicy
:拋棄策略,也就是直接忽略提交的任務(通俗來說就是空實現)。DiscardOldestPolicy
:拋棄最老任務策略,也就是通過poll()
方法取出任務隊列隊頭的任務拋棄,然後執行當前提交的任務。CallerRunsPolicy
:調用者執行策略,也就是當前調用Executor#execute()
的線程直接調用任務Runnable#run()
,一般不希望任務丟失會選用這種策略,但從實際角度來看,原來的異步調用意圖會退化爲同步調用。
狀態控制
狀態控制主要圍繞原子整型成員變量ctl
:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
接下來分析一下線程池的狀態變量,工作線程上限數量位的長度是COUNT_BITS
,它的值是Integer.SIZE - 3
,也就是正整數29:
我們知道,整型包裝類型Integer實例的大小是4 byte,一共32 bit,也就是一共有32個位用於存放0或者1。
在ThreadPoolExecutor實現中,使用32位的整型包裝類型存放工作線程數和線程池狀態。
其中,低29位用於存放工作線程數,而高3位用於存放線程池狀態,所以線程池的狀態最多隻能有2^3種。
工作線程上限數量爲2^29 - 1,超過5億,這個數量在短時間內不用考慮會超限。
接着看工作線程上限數量掩碼COUNT_MASK
,它的值是(1 < COUNT_BITS) - l
,也就是1左移29位,再減去1,如果補全32位,它的位視圖如下:
然後就是線程池的狀態常量,這裏只詳細分析其中一個,其他類同,這裏看RUNNING
狀態:
// -1的補碼爲:111-11111111111111111111111111111 |
控制變量ctl
的組成就是通過線程池運行狀態rs
和工作線程數wc
通過或運算得到的:
// rs=RUNNING值爲:111-00000000000000000000000000000 |
那麼我們怎麼從ctl
中取出高3位?上面源碼中提供的runStateOf()
方法就是提取運行狀態:
// 先把COUNT_MASK取反(~COUNT_MASK),得到:111-00000000000000000000000000000 |
同理,取出低29位只需要把ctl
和COUNT_MASK
(000-11111111111111111111111111111
)做一次與運算即可。
小結一下線程池的運行狀態常量:
狀態名稱 | 位圖 | 十進制值 | 描述 |
---|---|---|---|
RUNNING |
111-00000000000000000000000000000 |
-536870912 | 運行中狀態,可以接收新的任務和執行任務隊列中的任務 |
SHUTDOWN |
000-00000000000000000000000000000 |
0 | shutdown狀態,不再接收新的任務,但是會執行任務隊列中的任務 |
STOP |
001-00000000000000000000000000000 |
536870912 | 停止狀態,不再接收新的任務,也不會執行任務隊列中的任務,中斷所有執行中的任務 |
TIDYING |
010-00000000000000000000000000000 |
1073741824 | 整理中狀態,所有任務已經終結,工作線程數爲0,過渡到此狀態的工作線程會調用鉤子方法terminated() |
TERMINATED |
011-00000000000000000000000000000 |
1610612736 | 終結狀態,鉤子方法terminated() 執行完畢 |
這裏有一個比較特殊的技巧,由於運行狀態值存放在高3位,所以可以直接通過十進制值(甚至可以忽略低29位,直接用ctl
進行比較,或者使用ctl
和線程池狀態常量進行比較)來比較和判斷線程池的狀態:
RUNNING(-536870912) < SHUTDOWN(0) < STOP(536870912) < TIDYING(1073741824) < TERMINATED(1610612736)
下面這三個方法就是使用這種技巧:
// ctl和狀態常量比較,判斷是否小於 |
最後是線程池狀態的躍遷圖:
PS:線程池源碼中有很多中間變量用了簡單的單字母表示,例如c就是表示ctl、wc就是表示worker count、rs就是表示running status。
execute方法源碼分析
線程池異步執行任務的方法實現是ThreadPoolExecutor#execute()
,源碼如下:
// 執行命令,其中命令(下面稱任務)對象是Runnable的實例 |
這裏簡單分析一下整個流程:
- 如果當前工作線程總數小於
corePoolSize
,則直接創建核心線程執行任務(任務實例會傳入直接用於構造工作線程實例)。 - 如果當前工作線程總數大於等於
corePoolSize
,判斷線程池是否處於運行中狀態,同時嘗試用非阻塞方法向任務隊列放入任務,這裏會二次檢查線程池運行狀態,如果當前工作線程數量爲0,則創建一個非核心線程並且傳入的任務對象爲null。 - 如果向任務隊列投放任務失敗(任務隊列已經滿了),則會嘗試創建非核心線程傳入任務實例執行。
- 如果創建非核心線程失敗,此時需要拒絕執行任務,調用拒絕策略處理任務。
這裏是一個疑惑點:爲什麼需要二次檢查線程池的運行狀態,當前工作線程數量爲0,嘗試創建一個非核心線程並且傳入的任務對象爲null?這個可以看API註釋:
如果一個任務成功加入任務隊列,我們依然需要二次檢查是否需要添加一個工作線程(因爲所有存活的工作線程有可能在最後一次檢查之後已經終結)或者執行當前方法的時候線程池是否已經shutdown了。所以我們需要二次檢查線程池的狀態,必須時把任務從任務隊列中移除或者在沒有可用的工作線程的前提下新建一個工作線程。
任務提交流程從調用者的角度來看如下:
addWorker方法源碼分析
boolean addWorker(Runnable firstTask, boolean core)
方法的第一的參數可以用於直接傳入任務實例,第二個參數用於標識將要創建的工作線程是否核心線程。方法源碼如下:
// 添加工作線程,如果返回false說明沒有新創建工作線程,如果返回true說明創建和啓動工作線程成功 |
筆者發現了Doug Lea
大神十分喜歡複雜的條件判斷,而且單行復雜判斷不喜歡加花括號,像下面這種代碼在他編寫的很多類庫中都比較常見:
if (runStateAtLeast(c, SHUTDOWN) |
上面的分析邏輯中需要注意一點,Worker
實例創建的同時,在其構造函數中會通過ThreadFactory
創建一個Java線程Thread
實例,後面會加鎖後二次檢查是否需要把Worker
實例添加到工作線程集合workers
中和是否需要啓動Worker
中持有的Thread
實例,只有啓動了Thread
實例實例,Worker
才真正開始運作,否則只是一個無用的臨時對象。Worker
本身也實現了Runnable
接口,它可以看成是一個Runnable
的適配器。
工作線程內部類Worker源碼分析
線程池中的每一個具體的工作線程被包裝爲內部類Worker
實例,Worker
繼承於AbstractQueuedSynchronizer(AQS)
,實現了Runnable
接口:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ |
Worker
的構造函數裏面的邏輯十分重要,通過ThreadFactory
創建的Thread
實例同時傳入Worker
實例,因爲Worker
本身實現了Runnable
,所以可以作爲任務提交到線程中執行。只要Worker
持有的線程實例w
調用Thread#start()
方法就能在合適時機執行Worker#run()
。簡化一下邏輯如下:
// addWorker()方法中構造 |
Worker
繼承自AQS
,這裏使用了AQS
的獨佔模式,這裏有個技巧是構造Worker
的時候,把AQS
的資源(狀態)通過setState(-1)
設置爲-1,這是因爲Worker
實例剛創建時AQS
中state
的默認值爲0,此時線程尚未啓動,不能在這個時候進行線程中斷,見Worker#interruptIfStarted()
方法。Worker
中兩個覆蓋AQS
的方法tryAcquire()
和tryRelease()
都沒有判斷外部傳入的變量,前者直接CAS(0,1)
,後者直接setState(0)
。接着看核心方法ThreadPoolExecutor#runWorker()
:
final void runWorker(Worker w) { |
這裏重點拆解分析一下判斷當前工作線程中斷狀態的代碼:
if ((runStateAtLeast(ctl.get(), STOP) || |
Thread.interrupted()
方法獲取線程的中斷狀態同時會清空該中斷狀態,這裏之所以會調用這個方法是因爲在執行上面這個if
邏輯同時外部有可能調用shutdownNow()
方法,shutdownNow()
方法中也存在中斷所有Worker
線程的邏輯,但是由於shutdownNow()
方法中會遍歷所有Worker
做線程中斷,有可能無法及時在任務提交到Worker
執行之前進行中斷,所以這個中斷邏輯會在Worker
內部執行,就是if
代碼塊的邏輯。這裏還要注意的是:STOP
狀態下會拒絕所有新提交的任務,不會再執行任務隊列中的任務,同時會中斷所有Worker
線程。也就是,即使任務Runnable已經runWorker()
中前半段邏輯取出,只要還沒走到調用其Runnable#run(),都有可能被中斷。假設剛好發生了進入if
代碼塊的邏輯同時外部調用了shutdownNow()
方法,那麼if
邏輯內會判斷線程中斷狀態並且重置,那麼shutdownNow()
方法中調用的interruptWorkers()
就不會因爲中斷狀態判斷出現問題導致二次中斷線程(會導致異常)。
小結一下上面runWorker()
方法的核心流程:
Worker
先執行一次解鎖操作,用於解除不可中斷狀態。- 通過
while
循環調用getTask()
方法從任務隊列中獲取任務(當然,首輪循環也有可能是外部傳入的firstTask任務實例)。 - 如果線程池更變爲
STOP
狀態,則需要確保工作線程是中斷狀態並且進行中斷處理,否則要保證工作線程必須不是中斷狀態。 - 執行任務實例
Runnale#run()
方法,任務實例執行之前和之後(包括正常執行完畢和異常執行情況)分別會調用鉤子方法beforeExecute()
和afterExecute()
。 while
循環跳出意味着runWorker()
方法結束和工作線程生命週期結束(Worker#run()
生命週期完結),會調用processWorkerExit()
處理工作線程退出的後續工作。
接下來分析一下從任務隊列中獲取任務的getTask()
方法和處理線程退出的後續工作的方法processWorkerExit()
。
getTask方法源碼分析
getTask()
方法是工作線程在while
死循環中獲取任務隊列中的任務對象的方法:
private Runnable getTask() { |
這個方法中,有兩處十分龐大的if
邏輯,對於第一處if
可能導致工作線程數減去1直接返回null
的場景有:
- 線程池狀態爲
SHUTDOWN
,一般是調用了shutdown()
方法,並且任務隊列爲空。 - 線程池狀態爲
STOP
。
對於第二處if
,邏輯有點複雜,先拆解一下:
// 工作線程總數大於maximumPoolSize,說明了通過setMaximumPoolSize()方法減少了線程池容量 |
這段邏輯大多數情況下是針對非核心線程。在execute()
方法中,當線程池總數已經超過了corePoolSize
並且還小於maximumPoolSize
時,當任務隊列已經滿了的時候,會通過addWorker(task,false)
添加非核心線程。而這裏的邏輯恰好類似於addWorker(task,false)
的反向操作,用於減少非核心線程,使得工作線程總數趨向於corePoolSize
。如果對於非核心線程,上一輪循環獲取任務對象爲null
,這一輪循環很容易滿足timed && timedOut
爲true,這個時候getTask()
返回null會導致Worker#runWorker()
方法跳出死循環,之後執行processWorkerExit()
方法處理後續工作,而該非核心線程對應的Worker
則變成“遊離對象”,等待被JVM回收。當allowCoreThreadTimeOut
設置爲true的時候,這裏分析的非核心線程的生命週期終結邏輯同時會適用於核心線程。那麼可以總結出keepAliveTime
的意義:
- 當允許核心線程超時,也就是
allowCoreThreadTimeOut
設置爲true的時候,此時keepAliveTime
表示空閒的工作線程的存活週期。 - 默認情況下不允許核心線程超時,此時
keepAliveTime
表示空閒的非核心線程的存活週期。
在一些特定的場景下,配置合理的keepAliveTime
能夠更好地利用線程池的工作線程資源。
processWorkerExit方法源碼分析
processWorkerExit()
方法是爲將要終結的Worker
做一次清理和數據記錄工作(因爲processWorkerExit()
方法也包裹在runWorker()
方法finally
代碼塊中,其實工作線程在執行完processWorkerExit()
方法纔算真正的終結)。
private void processWorkerExit(Worker w, boolean completedAbruptly) { |
代碼的後面部分區域,會判斷線程池的狀態,如果線程池是RUNNING
或者SHUTDOWN
狀態的前提下,如果當前的工作線程由於拋出用戶異常被終結,那麼會新創建一個非核心線程。如果當前的工作線程並不是拋出用戶異常被終結(正常情況下的終結),那麼會這樣處理:
allowCoreThreadTimeOut
爲true,也就是允許核心線程超時的前提下,如果任務隊列空,則會通過創建一個非核心線程保持線程池中至少有一個工作線程。allowCoreThreadTimeOut
爲false,如果工作線程總數大於corePoolSize
則直接返回,否則創建一個非核心線程,也就是會趨向於保持線程池中的工作線程數量趨向於corePoolSize
。
processWorkerExit()
執行完畢之後,意味着該工作線程的生命週期已經完結。
tryTerminate方法源碼分析
每個工作線程終結的時候都會調用tryTerminate()
方法:
final void tryTerminate() { |
這裏有疑惑的地方是tryTerminate()
方法的第二個if
代碼邏輯:工作線程數不爲0,則中斷工作線程集合中的第一個空閒的工作線程。方法API註釋中有這樣一段話:
If otherwise eligible to terminate but workerCount is nonzero, interrupts an idle worker to ensure that shutdown signals propagate.
當滿足終結線程池的條件但是工作線程數不爲0,這個時候需要中斷一個空閒的工作線程去確保線程池關閉的信號得以傳播。
下面將會分析的shutdown()
方法中會通過interruptIdleWorkers()
中斷所有的空閒線程,這個時候有可能有非空閒的線程在執行某個任務,執行任務完畢之後,如果它剛好是核心線程,就會在下一輪循環阻塞在任務隊列的take()
方法,如果不做額外的干預,它甚至會在線程池關閉之後永久阻塞在任務隊列的take()
方法中。爲了避免這種情況,每個工作線程退出的時候都會嘗試中斷工作線程集合中的某一個空閒的線程,確保所有空閒的線程都能夠正常退出。
interruptIdleWorkers()
方法中會對每一個工作線程先進行tryLock()
判斷,只有返回true
纔有可能進行線程中斷。我們知道runWorker()
方法中,工作線程在每次從任務隊列中獲取到非null的任務之後,會先進行加鎖Worker#lock()
操作,這樣就能避免線程在執行任務的過程中被中斷,保證被中斷的一定是空閒的工作線程。
shutdown方法源碼分析
線程池關閉操作有幾個相關的變體方法,先看shutdown()
:
public void shutdown() { |
接着看shutdownNow()
方法:
public List<Runnable> shutdownNow() { |
shutdownNow()
方法會把線程池狀態先更變爲STOP
,中斷所有的工作線程(AbstractQueuedSynchronizer
的state
值大於0的Worker
實例,也就是包括正在執行任務的Worker
和空閒的Worker
),然後遍歷任務隊列,取出(移除)所有任務存放在一個列表中返回。
最後看awaitTermination()
方法:
public boolean awaitTermination(long timeout, TimeUnit unit) |
awaitTermination()
雖然不是shutdown()
方法體系,但是它的處理邏輯就是確保調用此方法的線程會阻塞到tryTerminate()
方法成功把線程池狀態更新爲TERMINATED
後再返回,可以使用在某些需要感知線程池終結時刻的場景。
有一點值得關注的是:shutdown()
方法只會中斷空閒的工作線程,如果工作線程正在執行任務對象Runnable#run()
,這種情況下的工作線程不會中斷,而是等待下一輪執行getTask()
方法的時候通過線程池狀態判斷正常終結該工作線程。
理解可重入鎖mainLock成員變量
private final ReentrantLock mainLock = new ReentrantLock();
private final Condition termination = mainLock.newCondition();
先看了ThreadPoolExecutor
內部成員屬性mainLock
的引用情況:
歸結一下mainLock
的使用場景:
方法 | 主要作用 |
---|---|
tryTerminate |
保證狀態TIDYING -> TERMINATED ,鉤子方法terminated() 回調和條件變量喚醒 |
interruptIdleWorkers |
保護工作線程中斷的串行化,避免”中斷風暴” |
addWorker |
保護工作線程集合避免併發增加工作線程、保護度量統計數據變更 |
processWorkerExit |
保護度量統計數據變更 |
shutdown 、shutdownNow 和awaitTermination |
見下文分析 |
getPoolSize 、getActiveCount 、getLargestPoolSize 、getTaskCount 和getCompletedTaskCount |
保護度量統計數據讀取,這些統計數據來一般源於Worker 集合的屬性統計 |
這裏分析一下線程池如何通過可重入鎖和條件變量實現相對優雅地關閉。先看shutdown()
方法:
public void shutdown() { |
這裏shutdown()
中除了tryTerminate()
,其他它方法都是包裹在鎖裏面執行,**確保工作線程集合穩定性以及關閉權限、確保狀態變更串行化,中斷所有工作線程並且避免工作線程”中斷風暴”**(多次併發調用shutdown()
如果不加鎖,會反覆中斷工作線程)。
public List<Runnable> shutdownNow() { |
shutdownNow()
方法其實加鎖的目的和shutdown()
差不多,不過多了一步:導出任務隊列中的剩餘的任務實例列表。awaitTermination()
方法中使用到前面提到過的條件變量termination
:
// 條件變量必須在鎖代碼塊中執行,和synchronized關鍵字用法差不多 |
awaitTermination()
方法的核心功能是:確保當前調用awaitTermination()
方法的線程阻塞等待對應的時間或者線程池狀態變更爲TERMINATED
,再退出等待返回結果,這樣能夠讓使用者輸入一個可以接受的等待時間進行阻塞等待,或者線程池在其他線程中被調用了shutdown()
方法狀態變更爲TERMINATED
就能正常解除阻塞。awaitTermination()
方法的返回值爲布爾值,true
代表線程池狀態變更爲TERMINATED
或者等待了輸入時間範圍內的時間週期被喚醒,意味則線程池正常退出,結果爲false
代表等待了超過輸入時間範圍內的時間週期,線程池的狀態依然沒有更變爲TERMINATED
。
線程池中的工作線程如何優雅地退出,不導致當前任務執行丟失、任務狀態異常或者任務持有的數據異常,是一個很值得探討的專題,以後有機會一定會分析一下這個專題。
reject方法源碼分析
reject(Runnable command)
方法很簡單:
final void reject(Runnable command) { |
調用線程池持有的成員RejectedExecutionHandler
實例回調任務實例和當前線程池實例。
鉤子方法分析
到JDK11
爲止,ThreadPoolExecutor
提供的鉤子方法沒有增加,有以下幾個:
beforeExecute(Thread t, Runnable r)
:任務對象Runnable#run()
執行之前觸發回調。afterExecute(Runnable r, Throwable t)
:任務對象Runnable#run()
執行之後(包括異常完成情況和正常完成情況)觸發回調。terminated()
:線程池關閉的時候,狀態更變爲TIDYING
成功之後會回調此方法,執行此方法完畢後,線程池狀態會更新爲TERMINATED
。onShutdown()
:shutdown()
方法執行時候會回調此方法,API註釋中提到此方法主要提供給ScheduledThreadPoolExecutor
使用。
其中onShutdown()
的方法修飾符爲default
,其他三個方法的修飾符爲protected
,必要時候可以自行擴展這些方法,可以實現監控、基於特定時機觸發具體操作等等。
其他方法
線程池本身提供了大量數據統計相關的方法、擴容方法、預創建方法等等,這些方法的源碼並不複雜,這裏不做展開分析。
核心線程相關:
getCorePoolSize()
:獲取核心線程數。setCorePoolSize()
:重新設置線程池的核心線程數。prestartCoreThread()
:預啓動一個核心線程,當且僅當工作線程數量小於核心線程數量。prestartAllCoreThreads()
:預啓動所有核心線程。
線程池容量相關:
getMaximumPoolSize()
:獲取線程池容量。setMaximumPoolSize()
:重新設置線程池的最大容量。
線程存活週期相關:
setKeepAliveTime()
:設置空閒工作線程的存活週期。getKeepAliveTime()
:獲取空閒工作線程的存活週期。
其他監控統計相關方法:
getTaskCount()
:獲取所有已經被執行的任務總數的近似值。getCompletedTaskCount()
:獲取所有已經執行完成的任務總數的近似值。getLargestPoolSize()
:獲取線程池的峯值線程數(最大池容量)。getActiveCount()
:獲取所有活躍線程總數(正在執行任務的工作線程)的近似值。getPoolSize()
:獲取工作線程集合的容量(當前線程池中的總工作線程數)。
任務隊列操作相關方法:
purge()
:移除任務隊列中所有是Future
類型並且已經處於Cancelled
狀態的任務。remove()
:從任務隊列中移除指定的任務。BlockingQueue<Runnable> getQueue()
:獲取任務隊列的引用。
有部分屬性值的設置有可能影響到線程池中的狀態或者工作線程的增減等,例如核心線程數改變,有可能會直接增減Worker
,這裏就以ThreadPoolExecutor#setCorePoolSize()
爲例:
// 設置核心線程數量 |
這裏else if (delta > 0)後面的代碼塊中有一段描述,翻譯一下:我們並不知道真正情況下”需要”多少新的工作線程。作爲一種啓發式處理方式,預先啓動足夠多的新的工作線程(直到數量爲核心線程池大小)來處理隊列中當前的任務,但如果在這樣做時隊列變爲空,則停止創建新的工作線程。
小結
本文花大量功夫基於每一行代碼分析JUC
線程池ThreadPoolExecutor
的核心方法execute()
的實現,這個方法是整個線程池相關體系的基石,有了它才能擴展出帶回調的異步執行和基於時間進行任務調度的功能,後面將會編寫兩篇文章分別詳細分析線程池擴展服務ExecutorService
的功能源碼實現以及調度線程池ScheduledThreadPoolExecutor
的源碼實現,預計要耗時2-3周。
修訂記錄
序號 | 時間 | 提議者 | 修訂描述 |
---|---|---|---|
1 | 2020-03-11 |
小馬過河 的Valine 評論 |
修正shutdownNow() 方法源碼分析的紕漏 |
2 | 2020-08-23 |
雄哥 |
指出可重入鎖內部屬性mainLock 的作用 |