硬核乾貨:4W字從源碼上分析JUC執行緒池ThreadPoolExecutor的實現原理
前提
很早之前就打算看一次JUC執行緒池ThreadPoolExecutor
的源碼實現,由於近段時間比較忙,一直沒有時間整理出源碼分析的文章。之前在分析擴展執行緒池實現可回調的Future
時候曾經提到並發大師Doug Lea
在設計執行緒池ThreadPoolExecutor
的提交任務的頂層介面Executor
只有一個無狀態的執行方法:
public interface Executor {
void execute(Runnable command);
}
而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 {
private BlockingQueue<Runnable> workQueue;
private static final AtomicInteger COUNTER = new AtomicInteger();
private int coreSize;
private int threadCount = 0;
public CoreThreadPool(int coreSize) {
this.coreSize = coreSize;
this.workQueue = new LinkedBlockingQueue<>();
}
@Override
public void execute(Runnable command) {
if (++threadCount <= coreSize) {
new Worker(command).start();
} else {
try {
workQueue.put(command);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
private class Worker extends Thread {
private Runnable firstTask;
public Worker(Runnable runnable) {
super(String.format("Worker-%d", COUNTER.getAndIncrement()));
this.firstTask = runnable;
}
@Override
public void run() {
Runnable task = this.firstTask;
while (null != task || null != (task = getTask())) {
try {
task.run();
} finally {
task = null;
}
}
}
}
private Runnable getTask() {
try {
return workQueue.take();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
public static void main(String[] args) throws Exception {
CoreThreadPool pool = new CoreThreadPool(5);
IntStream.range(0, 10)
.forEach(i -> pool.execute(() ->
System.out.println(String.format("Thread:%s,value:%d", Thread.currentThread().getName(), i))));
Thread.sleep(Integer.MAX_VALUE);
}
}
某次運行結果如下:
Thread:Worker-0,value:0
Thread:Worker-3,value:3
Thread:Worker-2,value:2
Thread:Worker-1,value:1
Thread:Worker-4,value:4
Thread:Worker-1,value:5
Thread:Worker-2,value:8
Thread:Worker-4,value:7
Thread:Worker-0,value:6
Thread:Worker-3,value:9
設計此執行緒池的時候,核心執行緒是懶創建的,如果執行緒空閑的時候則阻塞在任務隊列的take()
方法,其實對於ThreadPoolExecutor
也是類似這樣實現,只是如果使用了keepAliveTime
並且允許核心執行緒超時(allowCoreThreadTimeOut
設置為true
)則會使用BlockingQueue#poll(keepAliveTime)
進行輪詢代替永久阻塞。
其他附加功能
構建ThreadPoolExecutor
實例的時候,需要定義maximumPoolSize
(執行緒池最大執行緒數)和corePoolSize
(核心執行緒數)。當任務隊列是有界的阻塞隊列,核心執行緒滿負載,任務隊列已經滿的情況下,會嘗試創建額外的maximumPoolSize - corePoolSize
個執行緒去執行新提交的任務。當ThreadPoolExecutor
這裡實現的兩個主要附加功能是:
- 一定條件下會創建非核心執行緒去執行任務,非核心執行緒的回收周期(執行緒生命周期終結時刻)是
keepAliveTime
,執行緒生命周期終結的條件是:下一次通過任務隊列獲取任務的時候並且存活時間超過keepAliveTime
。 - 提供拒絕策略,也就是在核心執行緒滿負載、任務隊列已滿、非核心執行緒滿負載的條件下會觸發拒絕策略。
源碼分析
先分析執行緒池的關鍵屬性,接著分析其狀態控制,最後重點分析ThreadPoolExecutor#execute()
方法。
關鍵屬性
public class ThreadPoolExecutor extends AbstractExecutorService {
// 控制變數-存放狀態和執行緒數
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 任務隊列,必須是阻塞隊列
private final BlockingQueue<Runnable> workQueue;
// 工作執行緒集合,存放執行緒池中所有的(活躍的)工作執行緒,只有在持有全局鎖mainLock的前提下才能訪問此集合
private final HashSet<Worker> workers = new HashSet<>();
// 全局鎖
private final ReentrantLock mainLock = new ReentrantLock();
// awaitTermination方法使用的等待條件變數
private final Condition termination = mainLock.newCondition();
// 記錄峰值執行緒數
private int largestPoolSize;
// 記錄已經成功執行完畢的任務數
private long completedTaskCount;
// 執行緒工廠,用於創建新的執行緒實例
private volatile ThreadFactory threadFactory;
// 拒絕執行處理器,對應不同的拒絕策略
private volatile RejectedExecutionHandler handler;
// 空閑執行緒等待任務的時間周期,單位是納秒
private volatile long keepAliveTime;
// 是否允許核心執行緒超時,如果為true則keepAliveTime對核心執行緒也生效
private volatile boolean allowCoreThreadTimeOut;
// 核心執行緒數
private volatile int corePoolSize;
// 執行緒池容量
private volatile int maximumPoolSize;
// 省略其他程式碼
}
下面看參數列表最長的構造函數:
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以自定義核心執行緒數、執行緒池容量(最大執行緒數)、空閑執行緒等待任務周期、任務隊列、執行緒工廠、拒絕策略。下面簡單分析一下每個參數的含義和作用:
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));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// 通過ctl值獲取運行狀態
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
// 通過ctl值獲取工作執行緒數
private static int workerCountOf(int c) { return c & COUNT_MASK; }
// 通過運行狀態和工作執行緒數計算ctl的值,或運算
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
// CAS操作執行緒數增加1
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
// CAS操作執行緒數減少1
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
// 執行緒數直接減少1
private void decrementWorkerCount() {
ctl.addAndGet(-1);
}
接下來分析一下執行緒池的狀態變數,工作執行緒上限數量位的長度是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
// 左移29位後:111-00000000000000000000000000000
// 10進位值為:-536870912
// 高3位111的值就是表示執行緒池正在處於運行狀態
private static final int RUNNING = -1 << COUNT_BITS;
控制變數ctl
的組成就是通過執行緒池運行狀態rs
和工作執行緒數wc
通過或運算得到的:
// rs=RUNNING值為:111-00000000000000000000000000000
// wc的值為0:000-00000000000000000000000000000
// rs | wc的結果為:111-00000000000000000000000000000
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
那麼我們怎麼從ctl
中取出高3位?上面源碼中提供的runStateOf()
方法就是提取運行狀態:
// 先把COUNT_MASK取反(~COUNT_MASK),得到:111-00000000000000000000000000000
// ctl點陣圖特點是:xxx-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
// 兩者做一次與運算即可得到高3位xxx
private static int runStateOf(int c){
return c & ~COUNT_MASK;
}
同理,取出低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和狀態常量比較,判斷是否小於
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
// ctl和狀態常量比較,判斷是否小於或等於
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
// ctl和狀態常量SHUTDOWN比較,判斷是否處於RUNNING狀態
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
最後是執行緒池狀態的躍遷圖:
PS:執行緒池源碼中有很多中間變數用了簡單的單字母表示,例如c就是表示ctl、wc就是表示worker count、rs就是表示running status。
execute方法源碼分析
執行緒池非同步執行任務的方法實現是ThreadPoolExecutor#execute()
,源碼如下:
// 執行命令,其中命令(下面稱任務)對象是Runnable的實例
public void execute(Runnable command) {
// 判斷命令(任務)對象非空
if (command == null)
throw new NullPointerException();
// 獲取ctl的值
int c = ctl.get();
// 判斷如果當前工作執行緒數小於核心執行緒數,則創建新的核心執行緒並且執行傳入的任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
// 如果創建新的核心執行緒成功則直接返回
return;
// 這裡說明創建核心執行緒失敗,需要更新ctl的臨時變數c
c = ctl.get();
}
// 走到這裡說明創建新的核心執行緒失敗,也就是當前工作執行緒數大於等於corePoolSize
// 判斷執行緒池是否處於運行中狀態,同時嘗試用非阻塞方法向任務隊列放入任務(放入任務失敗返回false)
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 這裡是向任務隊列投放任務成功,對執行緒池的運行中狀態做二次檢查
// 如果執行緒池二次檢查狀態是非運行中狀態,則從任務隊列移除當前的任務調用拒絕策略處理之(也就是移除前面成功入隊的任務實例)
if (! isRunning(recheck) && remove(command))
// 調用拒絕策略處理任務 - 返回
reject(command);
// 走到下面的else if分支,說明有以下的前提:
// 0、待執行的任務已經成功加入任務隊列
// 1、執行緒池可能是RUNNING狀態
// 2、傳入的任務可能從任務隊列中移除失敗(移除失敗的唯一可能就是任務已經被執行了)
// 如果當前工作執行緒數量為0,則創建一個非核心執行緒並且傳入的任務對象為null - 返回
// 也就是創建的非核心執行緒不會馬上運行,而是等待獲取任務隊列的任務去執行
// 如果前工作執行緒數量不為0,原來應該是最後的else分支,但是可以什麼也不做,因為任務已經成功入隊列,總會有合適的時機分配其他空閑執行緒去執行它
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 走到這裡說明有以下的前提:
// 0、執行緒池中的工作執行緒總數已經大於等於corePoolSize(簡單來說就是核心執行緒已經全部懶創建完畢)
// 1、執行緒池可能不是RUNNING狀態
// 2、執行緒池可能是RUNNING狀態同時任務隊列已經滿了
// 如果向任務隊列投放任務失敗,則會嘗試創建非核心執行緒傳入任務執行
// 創建非核心執行緒失敗,此時需要拒絕執行任務
else if (!addWorker(command, false))
// 調用拒絕策略處理任務 - 返回
reject(command);
}
這裡簡單分析一下整個流程:
- 如果當前工作執行緒總數小於
corePoolSize
,則直接創建核心執行緒執行任務(任務實例會傳入直接用於構造工作執行緒實例)。 - 如果當前工作執行緒總數大於等於
corePoolSize
,判斷執行緒池是否處於運行中狀態,同時嘗試用非阻塞方法向任務隊列放入任務,這裡會二次檢查執行緒池運行狀態,如果當前工作執行緒數量為0,則創建一個非核心執行緒並且傳入的任務對象為null。 - 如果向任務隊列投放任務失敗(任務隊列已經滿了),則會嘗試創建非核心執行緒傳入任務實例執行。
- 如果創建非核心執行緒失敗,此時需要拒絕執行任務,調用拒絕策略處理任務。
這裡是一個疑惑點:為什麼需要二次檢查執行緒池的運行狀態,當前工作執行緒數量為0,嘗試創建一個非核心執行緒並且傳入的任務對象為null?這個可以看API注釋:
如果一個任務成功加入任務隊列,我們依然需要二次檢查是否需要添加一個工作執行緒(因為所有存活的工作執行緒有可能在最後一次檢查之後已經終結)或者執行當前方法的時候執行緒池是否已經shutdown了。所以我們需要二次檢查執行緒池的狀態,必須時把任務從任務隊列中移除或者在沒有可用的工作執行緒的前提下新建一個工作執行緒。
任務提交流程從調用者的角度來看如下:
addWorker方法源碼分析
boolean addWorker(Runnable firstTask, boolean core)
方法的第一的參數可以用於直接傳入任務實例,第二個參數用於標識將要創建的工作執行緒是否核心執行緒。方法源碼如下:
// 添加工作執行緒,如果返回false說明沒有新創建工作執行緒,如果返回true說明創建和啟動工作執行緒成功
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 注意這是一個死循環 - 最外層循環
for (int c = ctl.get();;) {
// 這個是十分複雜的條件,這裡先拆分多個與(&&)條件:
// 1. 執行緒池狀態至少為SHUTDOWN狀態,也就是rs >= SHUTDOWN(0)
// 2. 執行緒池狀態至少為STOP狀態,也就是rs >= STOP(1),或者傳入的任務實例firstTask不為null,或者任務隊列為空
// 其實這個判斷的邊界是執行緒池狀態為shutdown狀態下,不會再接受新的任務,在此前提下如果狀態已經到了STOP、或者傳入任務不為空、或者任務隊列為空(已經沒有積壓任務)都不需要添加新的執行緒
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
// 注意這也是一個死循環 - 二層循環
for (;;) {
// 這裡每一輪循環都會重新獲取工作執行緒數wc
// 1. 如果傳入的core為true,表示將要創建核心執行緒,通過wc和corePoolSize判斷,如果wc >= corePoolSize,則返回false表示創建核心執行緒失敗
// 1. 如果傳入的core為false,表示將要創非建核心執行緒,通過wc和maximumPoolSize判斷,如果wc >= maximumPoolSize,則返回false表示創建非核心執行緒失敗
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 成功通過CAS更新工作執行緒數wc,則break到最外層的循環
if (compareAndIncrementWorkerCount(c))
break retry;
// 走到這裡說明了通過CAS更新工作執行緒數wc失敗,這個時候需要重新判斷執行緒池的狀態是否由RUNNING已經變為SHUTDOWN
c = ctl.get(); // Re-read ctl
// 如果執行緒池狀態已經由RUNNING已經變為SHUTDOWN,則重新跳出到外層循環繼續執行
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// 如果執行緒池狀態依然是RUNNING,CAS更新工作執行緒數wc失敗說明有可能是並發更新導致的失敗,則在內層循環重試即可
// else CAS failed due to workerCount change; retry inner loop
}
}
// 標記工作執行緒是否啟動成功
boolean workerStarted = false;
// 標記工作執行緒是否創建成功
boolean workerAdded = false;
Worker w = null;
try {
// 傳入任務實例firstTask創建Worker實例,Worker構造裡面會通過執行緒工廠創建新的Thread對象,所以下面可以直接操作Thread t = w.thread
// 這一步Worker實例已經創建,但是沒有加入工作執行緒集合或者啟動它持有的執行緒Thread實例
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 這裡需要全局加鎖,因為會改變一些指標值和非執行緒安全的集合
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
// 這裡主要在加鎖的前提下判斷ThreadFactory創建的執行緒是否存活或者判斷獲取鎖成功之後執行緒池狀態是否已經更變為SHUTDOWN
// 1. 如果執行緒池狀態依然為RUNNING,則只需要判斷執行緒實例是否存活,需要添加到工作執行緒集合和啟動新的Worker
// 2. 如果執行緒池狀態小於STOP,也就是RUNNING或者SHUTDOWN狀態下,同時傳入的任務實例firstTask為null,則需要添加到工作執行緒集合和啟動新的Worker
// 對於2,換言之,如果執行緒池處於SHUTDOWN狀態下,同時傳入的任務實例firstTask不為null,則不會添加到工作執行緒集合和啟動新的Worker
// 這一步其實有可能創建了新的Worker實例但是並不啟動(臨時對象,沒有任何強引用),這種Worker有可能成功下一輪GC被收集的垃圾對象
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 把創建的工作執行緒實例添加到工作執行緒集合
workers.add(w);
int s = workers.size();
// 嘗試更新歷史峰值工作執行緒數,也就是執行緒池峰值容量
if (s > largestPoolSize)
largestPoolSize = s;
// 這裡更新工作執行緒是否啟動成功標識為true,後面才會調用Thread#start()方法啟動真實的執行緒實例
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果成功添加工作執行緒,則調用Worker內部的執行緒實例t的Thread#start()方法啟動真實的執行緒實例
if (workerAdded) {
t.start();
// 標記執行緒啟動成功
workerStarted = true;
}
}
} finally {
// 執行緒啟動失敗,需要從工作執行緒集合移除對應的Worker
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
// 添加Worker失敗
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 從工作執行緒集合移除之
if (w != null)
workers.remove(w);
// wc數量減1
decrementWorkerCount();
// 基於狀態判斷嘗試終結執行緒池
tryTerminate();
} finally {
mainLock.unlock();
}
}
筆者發現了Doug Lea
大神十分喜歡複雜的條件判斷,而且單行複雜判斷不喜歡加花括弧,像下面這種程式碼在他編寫的很多類庫中都比較常見:
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
// ....
// 程式碼拆分一下如下
boolean atLeastShutdown = runStateAtLeast(c, SHUTDOWN); # rs >= SHUTDOWN(0)
boolean atLeastStop = runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty();
if (atLeastShutdown && atLeastStop){
return false;
}
上面的分析邏輯中需要注意一點,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{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
// 保存ThreadFactory創建的執行緒實例,如果ThreadFactory創建執行緒失敗則為null
final Thread thread;
// 保存傳入的Runnable任務實例
Runnable firstTask;
// 記錄每個執行緒完成的任務總數
volatile long completedTasks;
// 唯一的構造函數,傳入任務實例firstTask,注意可以為null
Worker(Runnable firstTask) {
// 禁止執行緒中斷,直到runWorker()方法執行
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 通過ThreadFactory創建執行緒實例,注意一下Worker實例自身作為Runnable用於創建新的執行緒實例
this.thread = getThreadFactory().newThread(this);
}
// 委託到外部的runWorker()方法,注意runWorker()方法是執行緒池的方法,而不是Worker的方法
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
// 是否持有獨佔鎖,state值為1的時候表示持有鎖,state值為0的時候表示已經釋放鎖
protected boolean isHeldExclusively() {
return getState() != 0;
}
// 獨佔模式下嘗試獲取資源,這裡沒有判斷傳入的變數,直接CAS判斷0更新為1是否成功,成功則設置獨佔執行緒為當前執行緒
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 獨佔模式下嘗試是否資源,這裡沒有判斷傳入的變數,直接把state設置為0
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 加鎖
public void lock() { acquire(1); }
// 嘗試加鎖
public boolean tryLock() { return tryAcquire(1); }
// 解鎖
public void unlock() { release(1); }
// 是否鎖定
public boolean isLocked() { return isHeldExclusively(); }
// 啟動後進行執行緒中斷,注意這裡會判斷執行緒實例的中斷標誌位是否為false,只有中斷標誌位為false才會中斷
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Worker
的構造函數裡面的邏輯十分重要,通過ThreadFactory
創建的Thread
實例同時傳入Worker
實例,因為Worker
本身實現了Runnable
,所以可以作為任務提交到執行緒中執行。只要Worker
持有的執行緒實例w
調用Thread#start()
方法就能在合適時機執行Worker#run()
。簡化一下邏輯如下:
// addWorker()方法中構造
Worker worker = createWorker();
// 通過執行緒池構造時候傳入
ThreadFactory threadFactory = getThreadFactory();
// Worker構造函數中
Thread thread = threadFactory.newThread(worker);
// addWorker()方法中啟動
thread.start();
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) {
// 獲取當前執行緒,實際上和Worker持有的執行緒實例是相同的
Thread wt = Thread.currentThread();
// 獲取Worker中持有的初始化時傳入的任務對象,這裡注意存放在臨時變數task中
Runnable task = w.firstTask;
// 設置Worker中持有的初始化時傳入的任務對象為null
w.firstTask = null;
// 由於Worker初始化時AQS中state設置為-1,這裡要先做一次解鎖把state更新為0,允許執行緒中斷
w.unlock(); // allow interrupts
// 記錄執行緒是否因為用戶異常終結,默認是true
boolean completedAbruptly = true;
try {
// 初始化任務對象不為null,或者從任務隊列獲取任務不為空(從任務隊列獲取到的任務會更新到臨時變數task中)
// getTask()由於使用了阻塞隊列,這個while循環如果命中後半段會處於阻塞或者超時阻塞狀態,getTask()返回為null會導致執行緒跳出死循環使執行緒終結
while (task != null || (task = getTask()) != null) {
// Worker加鎖,本質是AQS獲取資源並且嘗試CAS更新state由0更變為1
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 如果執行緒池正在停止(也就是由RUNNING或者SHUTDOWN狀態向STOP狀態變更),那麼要確保當前工作執行緒是中斷狀態
// 否則,要保證當前執行緒不是中斷狀態
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 鉤子方法,任務執行前
beforeExecute(wt, task);
try {
task.run();
// 鉤子方法,任務執行後 - 正常情況
afterExecute(task, null);
} catch (Throwable ex) {
// 鉤子方法,任務執行後 - 異常情況
afterExecute(task, ex);
throw ex;
}
} finally {
// 清空task臨時變數,這個很重要,否則while會死循環執行同一個task
task = null;
// 累加Worker完成的任務數
w.completedTasks++;
// Worker解鎖,本質是AQS釋放資源,設置state為0
w.unlock();
}
}
// 走到這裡說明某一次getTask()返回為null,執行緒正常退出
completedAbruptly = false;
} finally {
// 處理執行緒退出,completedAbruptly為true說明由於用戶異常導致執行緒非正常退出
processWorkerExit(w, completedAbruptly);
}
}
這裡重點拆解分析一下判斷當前工作執行緒中斷狀態的程式碼:
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
// 先簡化一下判斷邏輯,如下
// 判斷執行緒池狀態是否至少為STOP,rs >= STOP(1)
boolean atLeastStop = runStateAtLeast(ctl.get(), STOP);
// 判斷執行緒池狀態是否至少為STOP,同時判斷當前執行緒的中斷狀態並且清空當前執行緒的中斷狀態
boolean interruptedAndAtLeastStop = Thread.interrupted() && runStateAtLeast(ctl.get(), STOP);
if (atLeastStop || interruptedAndAtLeastStop && !wt.isInterrupted()){
wt.interrupt();
}
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() {
// 記錄上一次從隊列中拉取的時候是否超時
boolean timedOut = false; // Did the last poll() time out?
// 注意這是死循環
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
// 第一個if:如果執行緒池狀態至少為SHUTDOWN,也就是rs >= SHUTDOWN(0),則需要判斷兩種情況(或邏輯):
// 1. 執行緒池狀態至少為STOP(1),也就是執行緒池正在停止,一般是調用了shutdownNow()方法
// 2. 任務隊列為空
// 如果在執行緒池至少為SHUTDOWN狀態並且滿足上面兩個條件之一,則工作執行緒數wc減去1,然後直接返回null
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 跑到這裡說明執行緒池還處於RUNNING狀態,重新獲取一次工作執行緒數
int wc = workerCountOf(c);
// Are workers subject to culling?
// timed臨時變數勇於執行緒超時控制,決定是否需要通過poll()此帶超時的非阻塞方法進行任務隊列的任務拉取
// 1.allowCoreThreadTimeOut默認值為false,如果設置為true,則允許核心執行緒也能通過poll()方法從任務隊列中拉取任務
// 2.工作執行緒數大於核心執行緒數的時候,說明執行緒池中創建了額外的非核心執行緒,這些非核心執行緒一定是通過poll()方法從任務隊列中拉取任務
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 第二個if:
// 1.wc > maximumPoolSize說明當前的工作執行緒總數大於maximumPoolSize,說明了通過setMaximumPoolSize()方法減少了執行緒池容量
// 或者 2.timed && timedOut說明了執行緒命中了超時控制並且上一輪循環通過poll()方法從任務隊列中拉取任務為null
// 並且 3. 工作執行緒總數大於1或者任務隊列為空,則通過CAS把執行緒數減去1,同時返回null,
// CAS把執行緒數減去1失敗會進入下一輪循環做重試
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果timed為true,通過poll()方法做超時拉取,keepAliveTime時間內沒有等待到有效的任務,則返回null
// 如果timed為false,通過take()做阻塞拉取,會阻塞到有下一個有效的任務時候再返回(一般不會是null)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 這裡很重要,只有非null時候才返回,null的情況下會進入下一輪循環
if (r != null)
return r;
// 跑到這裡說明上一次從任務隊列中獲取到的任務為null,一般是workQueue.poll()方法超時返回null
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
這個方法中,有兩處十分龐大的if
邏輯,對於第一處if
可能導致工作執行緒數減去1直接返回null
的場景有:
- 執行緒池狀態為
SHUTDOWN
,一般是調用了shutdown()
方法,並且任務隊列為空。 - 執行緒池狀態為
STOP
。
對於第二處if
,邏輯有點複雜,先拆解一下:
// 工作執行緒總數大於maximumPoolSize,說明了通過setMaximumPoolSize()方法減少了執行緒池容量
boolean b1 = wc > maximumPoolSize;
// 允許執行緒超時同時上一輪通過poll()方法從任務隊列中拉取任務為null
boolean b2 = timed && timedOut;
// 工作執行緒總數大於1
boolean b3 = wc > 1;
// 任務隊列為空
boolean b4 = workQueue.isEmpty();
boolean r = (b1 || b2) && (b3 || b4);
if (r) {
if (compareAndDecrementWorkerCount(c)){
return null;
}else{
continue;
}
}
這段邏輯大多數情況下是針對非核心執行緒。在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) {
// 因為拋出用戶異常導致執行緒終結,直接使工作執行緒數減1即可
// 如果沒有任何異常拋出的情況下是通過getTask()返回null引導執行緒正常跳出runWorker()方法的while死循環從而正常終結,這種情況下,在getTask()中已經把執行緒數減1
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 全局的已完成任務記錄數加上此將要終結的Worker中的已完成任務數
completedTaskCount += w.completedTasks;
// 工作執行緒集合中移除此將要終結的Worker
workers.remove(w);
} finally {
mainLock.unlock();
}
// 見下一小節分析,用於根據當前執行緒池的狀態判斷是否需要進行執行緒池terminate處理
tryTerminate();
int c = ctl.get();
// 如果執行緒池的狀態小於STOP,也就是處於RUNNING或者SHUTDOWN狀態的前提下:
// 1.如果執行緒不是由於拋出用戶異常終結,如果允許核心執行緒超時,則保持執行緒池中至少存在一個工作執行緒
// 2.如果執行緒由於拋出用戶異常終結,或者當前工作執行緒數,那麼直接添加一個新的非核心執行緒
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// 如果允許核心執行緒超時,最小值為0,否則為corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果最小值為0,同時任務隊列不空,則更新最小值為1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 工作執行緒數大於等於最小值,直接返回不新增非核心執行緒
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
程式碼的後面部分區域,會判斷執行緒池的狀態,如果執行緒池是RUNNING
或者SHUTDOWN
狀態的前提下,如果當前的工作執行緒由於拋出用戶異常被終結,那麼會新創建一個非核心執行緒。如果當前的工作執行緒並不是拋出用戶異常被終結(正常情況下的終結),那麼會這樣處理:
allowCoreThreadTimeOut
為true,也就是允許核心執行緒超時的前提下,如果任務隊列空,則會通過創建一個非核心執行緒保持執行緒池中至少有一個工作執行緒。allowCoreThreadTimeOut
為false,如果工作執行緒總數大於corePoolSize
則直接返回,否則創建一個非核心執行緒,也就是會趨向於保持執行緒池中的工作執行緒數量趨向於corePoolSize
。
processWorkerExit()
執行完畢之後,意味著該工作執行緒的生命周期已經完結。
tryTerminate方法源碼分析
每個工作執行緒終結的時候都會調用tryTerminate()
方法:
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 判斷執行緒池的狀態,如果是下面三種情況下的任意一種則直接返回:
// 1.執行緒池處於RUNNING狀態
// 2.執行緒池至少為TIDYING狀態,也就是TIDYING或者TERMINATED狀態,意味著已經走到了下面的步驟,執行緒池即將終結
// 3.執行緒池至少為STOP狀態並且任務隊列不為空
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
return;
// 工作執行緒數不為0,則中斷工作執行緒集合中的第一個空閑的工作執行緒
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// CAS設置執行緒池狀態為TIDYING,如果設置成功則執行鉤子方法terminated()
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
// 最後更新執行緒池狀態為TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 喚醒阻塞在termination條件的所有執行緒,這個變數的await()方法在awaitTermination()中調用
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
// 中斷空閑的工作執行緒,onlyOne為true的時候,只會中斷工作執行緒集合中的某一個執行緒
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 這裡判斷執行緒不是中斷狀態並且嘗試獲取鎖成功的時候才進行執行緒中斷
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 這裡跳出循環,也就是只中斷集合中第一個工作執行緒
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
這裡有疑惑的地方是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() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 許可權校驗,安全策略相關判斷
checkShutdownAccess();
// 設置SHUTDOWN狀態
advanceRunState(SHUTDOWN);
// 中斷所有的空閑的工作執行緒
interruptIdleWorkers();
// 鉤子方法
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 調用上面分析果敢的嘗試terminate方法,使狀態更變為TIDYING,執行鉤子方法terminated()後,最終狀態更新為TERMINATED
tryTerminate();
}
// 升提狀態
private void advanceRunState(int targetState) {
// assert targetState == SHUTDOWN || targetState == STOP;
for (;;) {
int c = ctl.get();
// 執行緒池狀態至少為targetState或者CAS設置狀態為targetState則跳出循環
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
// 中斷所有的空閑的工作執行緒
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
接著看shutdownNow()
方法:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 許可權校驗,安全策略相關判斷
checkShutdownAccess();
// 設置STOP狀態
advanceRunState(STOP);
// 中斷所有的工作執行緒
interruptWorkers();
// 清空工作隊列並且取出所有的未執行的任務
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 調用上面分析果敢的嘗試terminate方法,使狀態更變為TIDYING,執行鉤子方法terminated()後,最終狀態更新為TERMINATED
tryTerminate();
return tasks;
}
// 遍歷所有的工作執行緒,如果state > 0(啟動狀態)則進行中斷
private void interruptWorkers() {
// assert mainLock.isHeldByCurrentThread();
for (Worker w : workers)
w.interruptIfStarted();
}
shutdownNow()
方法會把執行緒池狀態先更變為STOP
,中斷AbstractQueuedSynchronizer
的state
值大於0的Worker
實例,也就是包括正在執行任務的Worker
和空閑的Worker
),然後遍歷任務隊列,取出(移除)所有任務存放在一個列表中返回。
最後看awaitTermination()
方法:
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
// 轉換timeout的單位為納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 循環等待直到執行緒池狀態更變為TERMINATED,每輪循環等待nanos納秒
while (runStateLessThan(ctl.get(), TERMINATED)) {
if (nanos <= 0L)
return false;
nanos = termination.awaitNanos(nanos);
}
return true;
} finally {
mainLock.unlock();
}
}
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() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
這裡shutdown()
中除了tryTerminate()
,其他它方法都是包裹在鎖裡面執行,確保工作執行緒集合穩定性以及關閉許可權、確保狀態變更串列化,中斷所有工作執行緒並且避免工作執行緒”中斷風暴”(多次並發調用shutdown()
如果不加鎖,會反覆中斷工作執行緒)。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue(); # <--- 多了這一步
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
shutdownNow()
方法其實加鎖的目的和shutdown()
差不多,不過多了一步:導出任務隊列中的剩餘的任務實例列表。awaitTermination()
方法中使用到前面提到過的條件變數termination
:
// 條件變數必須在鎖程式碼塊中執行,和synchronized關鍵字用法差不多
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 死循環確保等待執行和狀態變更為TERMINATED
while (runStateLessThan(ctl.get(), TERMINATED)) {
if (nanos <= 0L)
return false;
nanos = termination.awaitNanos(nanos); # <-- 確保當前調用執行緒阻塞等待對應的時間或者執行緒池狀態變更為TERMINATED,再退出等待
}
return true;
} finally {
mainLock.unlock();
}
}
awaitTermination()
方法的核心功能是:確保當前調用awaitTermination()
方法的執行緒阻塞等待對應的時間或者執行緒池狀態變更為TERMINATED
,再退出等待返回結果,這樣能夠讓使用者輸入一個可以接受的等待時間進行阻塞等待,或者執行緒池在其他執行緒中被調用了shutdown()
方法狀態變更為TERMINATED
就能正常解除阻塞。awaitTermination()
方法的返回值為布爾值,true
代表執行緒池狀態變更為TERMINATED
或者等待了輸入時間範圍內的時間周期被喚醒,意味則執行緒池正常退出,結果為false
代表等待了超過輸入時間範圍內的時間周期,執行緒池的狀態依然沒有更變為TERMINATED
。
執行緒池中的工作執行緒如何優雅地退出,不導致當前任務執行丟失、任務狀態異常或者任務持有的數據異常,是一個很值得探討的專題,以後有機會一定會分析一下這個專題。
reject方法源碼分析
reject(Runnable command)
方法很簡單:
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
調用執行緒池持有的成員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()
為例:
// 設置核心執行緒數量
public void setCorePoolSize(int corePoolSize) {
// 輸入值不能小於0或者大於執行緒池的容量
if (corePoolSize < 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
// delta = 傳入核心執行緒數和現存的核心執行緒數的差值
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
// 如果當前執行緒池工作執行緒的總量大於傳入核心執行緒數,則中斷所有的工作執行緒
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
// 傳入核心執行緒數和現存的核心執行緒數的差值大於0,也就是核心執行緒擴容
// 計算傳入核心執行緒數和現存的核心執行緒數的差值和任務隊列中任務個數的最小值,並且添加這個最小值個數的工作執行緒池
// 任務隊列為空的情況下,k === 0,此時第一個條件 k--> 0就不滿足,不會進入循環,那麼這delta個需要創建的工作執行緒應該是在提交新任務的時候懶創建
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
// 如果任務隊列為空,則跳出循環
if (workQueue.isEmpty())
break;
}
}
}
這裡else if (delta > 0)後面的程式碼塊中有一段描述,翻譯一下:我們並不知道真正情況下”需要”多少新的工作執行緒。作為一種啟發式處理方式,預先啟動足夠多的新的工作執行緒(直到數量為核心執行緒池大小)來處理隊列中當前的任務,但如果在這樣做時隊列變為空,則停止創建新的工作執行緒。
小結
本文花大量功夫基於每一行程式碼分析JUC
執行緒池ThreadPoolExecutor
的核心方法execute()
的實現,這個方法是整個執行緒池相關體系的基石,有了它才能擴展出帶回調的非同步執行和基於時間進行任務調度的功能,後面將會編寫兩篇文章分別詳細分析執行緒池擴展服務ExecutorService
的功能源碼實現以及調度執行緒池ScheduledThreadPoolExecutor
的源碼實現,預計要耗時2-3周。
個人部落格原文://throwx.cn/2020/08/23/java-concurrency-thread-pool-executor/