(手機橫屏看源碼更方便)java
注:java源碼分析部分如無特殊說明均基於 java8 版本。源碼分析
注:線程池源碼部分如無特殊說明均指ThreadPoolExecutor類。學習
上一章咱們一塊兒重溫了下線程的生命週期(六種狀態還記得不?),可是你知不知道其實線程池也是有生命週期的呢?!this
(1)線程池的狀態有哪些?線程
(2)各類狀態下對於任務隊列中的任務有何影響?code
其實,在咱們講線程池體系結構的時候,講了一些方法,好比shutDown()/shutDownNow(),它們都是與線程池的生命週期相關聯的。blog
咱們先來看一下線程池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也是能夠的;rem
(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中分析。
本章咱們一塊兒從狀態定義、流程圖、源碼分析等多個角度一塊兒學習了線程池的生命週期,你掌握的怎麼樣呢?
下一章咱們將開始學習線程池執行任務的主流程,對這一塊內容感到恐懼的同窗能夠先看看彤哥以前寫的「手寫線程池」的兩篇文章,對接下來學習線程池的主要流程很是有好處。
歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。