本文只介紹 ThreadPoolExecutor
源碼的關鍵部分,開篇會先介紹 ThreadPoolExecutor
中的一些核心常量定義,然後選取線程池工作週期中的幾個關鍵方法分析其源碼實現。其實,看 JDK
源碼的最好途徑就是看類文件註釋,作者把想說的全都寫在裏面了。
一些重要的常量
ThreadPoolExecutor
內部作者採用了一個 32bit
的 int
值來表示線程池的運行狀態(runState)
和當前線程池中的線程數目(workerCount)
,這個變量取名叫 ctl
(control
的縮寫),其中高 3bit
表示允許狀態,低 29bit
表示線程數目(最多允許 2^29 - 1
個線程)。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 29 位
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 線程池最大容量
// runState is stored in the high-order bits
// 定義的線程池狀態常量
// 111+29個0,值爲 -4 + 2 + 1 = -1(不懂的面壁)
private static final int RUNNING = -1 << COUNT_BITS;
// 000+29個0
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001+29個0
private static final int STOP = 1 << COUNT_BITS;
// 010+29個0
private static final int TIDYING = 2 << COUNT_BITS;
// 011+29個0
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } // 得到線程池狀態
private static int workerCountOf(int c) { return c & CAPACITY; } // 得到線程池線程數
private static int ctlOf(int rs, int wc) { return rs | wc; } // 反向構造 ctl 的值
因爲代表線程池狀態的常量可以通過值的大小來表示先後關係(order)
,因此後續源碼中會有:
rs >= SHUTDOWN // 那就表示SHUTDOWN、 STOP or TIDYING or TERMINATED,反正不是 RUNNING
理解上述的常量意義有助於後面理解源碼。
討論線程池的狀態轉換
從第一節我們已經知道了線程池分爲五個狀態,下面我們聊聊這五個狀態分別限制了線程池能執行怎樣的行爲:
RUNNING:
可以接受新任務,且執行Queue
中的任務SHUTDOWN:
不再接受新的任務,但能繼續執行Queue
中已有的任務STOP:
不再接受新的任務,且也不再執行Queue
中已有的任務TIDYING:
所有任務完成,workCount=0
,線程池狀態轉爲TIDYING
且會執行hook method
,即terminated()
TERMINATED:``hook method
terminated()
執行完畢之後進入的狀態
線程池的關鍵邏輯
上圖總結了 ThreadPoolExecutor
源碼中的關鍵性步驟,正好對應我們此次解析的核心源碼(上圖出處見水印)。
execute
方法用來向線程池提交task
,這是用戶使用線程池的第一步。如果線程池內未達到corePoolSize
則新建一個線程,將該task
設置爲這個線程的firstTask
,然後加入workerSet
等待調度,這步需要獲取全局鎖mainLock
- 已達到
corePoolSize
後,將task
放入阻塞隊列 - 若阻塞隊列放不下,則新建新的線程來處理,這一步也需要獲取全局鎖
mainLock
- 當前線程池
workerCount
超出maxPoolSize
後用rejectHandler
來處理
我們可以看到,線程池的設計使得在 2
步驟時避免了使用全局鎖,只需要塞進隊列返回等待異步調度就可以,僅剩下 1
和 3
創建線程時需要獲取全局鎖,這有利於線程池的效率提升,因爲一個線程池總是大部分時間在步驟 2
上,否則這線程池也沒什麼存在的意義。
源碼分析
本文只分析 execute
,addWorker
,runWorker
,三個核心方法和一個 Worker
類,看懂了這幾個,其實其他的代碼都能看懂。
Worker 類
// 繼承自 AQS 實現簡單的鎖控制
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// worker 運行所在的線程
final Thread thread;
// 賦予該線程的第一個 task,可能是 null,如果不是 null 就運行這個,
// 如果是 null 就通過 getTask 方法去 Queue 裏取任務
Runnable firstTask;
// 線程完成的任務數量
volatile long completedTasks;
Worker(Runnable firstTask) {
// 限制線程直到 runWorker 方法前都不允許被打斷
setState(-1);
this.firstTask = firstTask;
// 線程工廠創建線程
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
// 線程內部的 run 方法調用了 runWorker 方法
runWorker(this);
}
}
execute 方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果當前線程數小於 corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 調用 addWorker 方法新建線程,如果新建成功返回 true,那麼 execute 方法結束
if (addWorker(command, true))
return;
// 這裏意味着 addWorker 失敗,向下執行,因爲 addWorker 可能改變 ctl 的值,
// 所以這裏重新獲取下 ctl
c = ctl.get();
}
// 到這步要麼是 corePoolSize 滿了,要麼是 addWorker 失敗了
// 前者很好理解,後者爲什麼會失敗呢?addWorker 中會講
// 如果線程池狀態爲 RUNNING 且 task 插入 Queue 成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果已不處於 RUNNING 狀態,那麼刪除已經入隊的 task,然後執行拒絕策略
// 這裏主要是擔心併發場景下有別的線程改變了線程池狀態,所以 double-check 下
if (! isRunning(recheck) && remove(command))
reject(command);
// 這個分支有點難以理解,意爲如果當前 workerCount=0 的話就創建一個線程
// 那爲什麼方法開頭的那個 addWorker(command, true) 會返回 false 呢,其實
// 這裏有個場景就是 newCachedThreadPool,corePoolSize=0,maxPoolSize=MAX 的場景,
// 就會進到這個分支,以 maxPoolSize 爲界創建臨時線程,firstTask=null
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 這個分支很好理解,workQueue 滿了那麼要根據 maxPoolSize 創建線程了
// 如果沒法創建說明 maxPoolSize 滿了,執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
addWorker 方法
// core 表示以 corePoolSize 還是 maxPoolSize 爲界
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 看看 addWorker 什麼時候返回 false
// 這裏的 if 邏輯有點難懂,用下數學上的分配率,將第一個邏輯表達式放進括號裏就好懂了
// 1、rs >= SHUTDOWN && rs != SHUTDOWN 其實就表示當線程池狀態是 STOP、TIDYING, 或 TERMINATED 的時候,當然不能添加 worker 了,任務都不執行了還想加 worker?
// 2、rs >= SHUTDOWN && firstTask != null 表示當提交一個非空任務,但線程池狀態已經不是 RUNNING 的時候,當然也不能 addWorker,因爲你最多隻能執行完 Queue 中已有的任務
// 3、rs >= SHUTDOWN && workQueue.isEmpty() 如果 Queue 已經空了,那麼不允許新增
// 需要注意的是,如果 rs=SHUTDOWN && firstTask=null 或者 rs=SHUTDOWN && workQueue 非空的情況下,還是可以新增 worker 的,需要創建臨時線程處理 Queue 裏的任務
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 這裏也是一個返回 false 的情況,但很簡單,就是數目溢出了
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS 成功了,就跳出 loop
if (compareAndIncrementWorkerCount(c))
break retry;
// CAS 失敗的話,check 下目前線程池狀態,如果發生改變就回到外層 loop 再來一遍,這個也好理解,否則單純 CAS 失敗但是線程池狀態不變的話,就只要繼續內層 loop 就行了
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 這是全局鎖,必須持有才能進行 addWorker 操作
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 rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && 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;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 啓動線程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
runWorker 方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 循環直至 task = null,可能是由於線程池關閉、等待超時等
while (task != null || (task = getTask()) != null) {
w.lock();
// 下面這個 if 邏輯沒怎麼讀懂。。。翻譯了下注釋
// 如果線程池停止,確保線程中斷;
// 如果沒有,確保線程不中斷。這需要在第二種情況下進行重新獲取ctl,以便在清除中斷時處理shutdownNow競爭
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 前置鉤子函數,可以自定義
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 運行 run 方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
// 線程的 run 不允許拋出 Throwable,所以轉換爲 Error
thrown = x; throw new Error(x);
} finally {
// 後置鉤子函數,也可以自定義
afterExecute(task, thrown);
}
} finally {
// 獲取下一個任務
task = null;
// 增加完成的任務數目
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
寫在最後
看完 ThreadPoolExecutor
的源碼,不得不驚歎於代碼寫得真優雅,但是正因爲寫的太簡潔優雅甚至找不到一句囉嗦的代碼,所以讓人有點難懂。看源碼的建議是先仔細閱讀一遍類註釋,然後再配合 debug
,理清關鍵性的步驟在做什麼,有些 corner case
夾雜在主邏輯裏面,如果一開始看不懂可以直接略過,事後再來反思。