(手機橫屏看源碼更方便) 註:java源碼分析部分如無特殊說明均基於 java8 版本。 註:線程池源碼部分如無特殊說明均指ThreadPoolExecutor類。 簡介 上一章我們一起重溫了下線程的生命周期(六種狀態還記得不?),但是你知不知道其實線程池也是有生命周期的呢?! 問題 (1)線程池的 ...
(手機橫屏看源碼更方便)
註:java源碼分析部分如無特殊說明均基於 java8 版本。
註:線程池源碼部分如無特殊說明均指ThreadPoolExecutor類。
簡介
上一章我們一起重溫了下線程的生命周期(六種狀態還記得不?),但是你知不知道其實線程池也是有生命周期的呢?!
問題
(1)線程池的狀態有哪些?
(2)各種狀態下對於任務隊列中的任務有何影響?
先上源碼
其實,在我們講線程池體繫結構的時候,講了一些方法,比如shutDown()/shutDownNow(),它們都是與線程池的生命周期相關聯的。
我們先來看一下線程池ThreadPoolExecutor中定義的生命周期中的狀態及相關方法:
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; // =000 11111...
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; // 111 00000...
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000 00000...
private static final int STOP = 1 << COUNT_BITS; // 001 00000...
private static final int TIDYING = 2 << COUNT_BITS; // 010 00000...
private static final int TERMINATED = 3 << COUNT_BITS; // 011 00000...
// 線程池的狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 線程池中工作線程的數量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 計算ctl的值,等於運行狀態“加上”線程數量
private static int ctlOf(int rs, int wc) { return rs | wc; }
從上面這段代碼,我們可以得出:
(1)線程池的狀態和工作線程的數量共同保存在控制變數ctl中,類似於AQS中的state變數,不過這裡是直接使用的AtomicInteger,這裡換成unsafe+volatile也是可以的;
(2)ctl的高三位保存運行狀態,低29位保存工作線程的數量,也就是說線程的數量最多只能有(2^29-1)個,也就是上面的CAPACITY;
(3)線程池的狀態一共有五種,分別是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED;
(4)RUNNING,表示可接受新任務,且可執行隊列中的任務;
(5)SHUTDOWN,表示不接受新任務,但可執行隊列中的任務;
(6)STOP,表示不接受新任務,且不再執行隊列中的任務,且中斷正在執行的任務;
(7)TIDYING,所有任務已經中止,且工作線程數量為0,最後變遷到這個狀態的線程將要執行terminated()鉤子方法,只會有一個線程執行這個方法;
(8)TERMINATED,中止狀態,已經執行完terminated()鉤子方法;
流程圖
下麵我們再來看看這些狀態之間是怎麼流轉的:
(1)新建線程池時,它的初始狀態為RUNNING,這個在上面定義ctl的時候可以看到;
(2)RUNNING->SHUTDOWN,執行shutdown()方法時;
(3)RUNNING->STOP,執行shutdownNow()方法時;
(4)SHUTDOWN->STOP,執行shutdownNow()方法時【本文由公從號“彤哥讀源碼”原創】;
(5)STOP->TIDYING,執行了shutdown()或者shutdownNow()後,所有任務已中止,且工作線程數量為0時,此時會執行terminated()方法;
(6)TIDYING->TERMINATED,執行完terminated()方法後;
源碼分析
你以為貼個狀態的源碼,畫個圖就結束了嘛?那肯定不能啊,下麵讓我們一起來看看源碼中是怎麼控制的。
(1)RUNNING
RUNNING,比較簡單,創建線程池的時候就會初始化ctl,而ctl初始化為RUNNING狀態,所以線程池的初始狀態就為RUNNING狀態。
// 初始狀態為RUNNING
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
(2)SHUTDOWN
執行shutdown()方法時把狀態修改為SHUTDOWN,這裡肯定會成功,因為advanceRunState()方法中是個自旋,不成功不會退出。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改狀態為SHUTDOWN
advanceRunState(SHUTDOWN);
// 標記空閑線程為中斷狀態
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
// 如果狀態大於SHUTDOWN,或者修改為SHUTDOWN成功了,才會break跳出自旋
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
(3)STOP
執行shutdownNow()方法時,會把線程池狀態修改為STOP狀態,同時標記所有線程為中斷狀態。
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();
}
tryTerminate();
return tasks;
}
至於線程是否響應中斷其實是在隊列的take()或poll()方法中響應的,最後會到AQS中,它們檢測到線程中斷了會拋出一個InterruptedException異常,然後getTask()中捕獲這個異常,並且在下一次的自旋時退出當前線程並減少工作線程的數量。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果狀態為STOP了,這裡會直接退出迴圈,且減少工作線程數量
// 退出迴圈了也就相當於這個線程的生命周期結束了
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 真正響應中斷是在poll()方法或者take()方法中
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
// 這裡捕獲中斷異常
timedOut = false;
}
}
}
這裡有一個問題,就是已經通過getTask()取出來且返回的任務怎麼辦?
實際上它們會正常執行完畢,有興趣的同學可以自己看看runWorker()這個方法,我們下一節會分析這個方法。
(4)TIDYING
當執行shutdown()或shutdownNow()之後,如果所有任務已中止,且工作線程數量為0,就會進入這個狀態。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 下麵幾種情況不會執行後續代碼
// 1. 運行中
// 2. 狀態的值比TIDYING還大,也就是TERMINATED
// 3. SHUTDOWN狀態且任務隊列不為空
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 工作線程數量不為0,也不會執行後續代碼
if (workerCountOf(c) != 0) {
// 嘗試中斷空閑的線程
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// CAS修改狀態為TIDYING狀態
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 更新成功,執行terminated鉤子方法
terminated();
} finally {
// 強制更新狀態為TERMINATED,這裡不需要CAS了
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
實際更新狀態為TIDYING和TERMINATED狀態的代碼都在tryTerminate()方法中,實際上tryTerminated()方法在很多地方都有調用,比如shutdown()、shutdownNow()、線程退出時,所以說幾乎每個線程最後消亡的時候都會調用tryTerminate()方法,但最後只會有一個線程真正執行到修改狀態為TIDYING的地方。
修改狀態為TIDYING後執行terminated()方法,最後修改狀態為TERMINATED,標志著線程池真正消亡了。
(5)TERMINATED
見TIDYING中分析。
彩蛋
本章我們一起從狀態定義、流程圖、源碼分析等多個角度一起學習了線程池的生命周期,你掌握的怎麼樣呢?
下一章我們將開始學習線程池執行任務的主流程,對這一塊內容感到恐懼的同學可以先看看彤哥之前寫的“手寫線程池”的兩篇文章,對接下來學習線程池的主要流程非常有好處。
歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。