死磕 java執行緒系列之執行緒池深入解析——生命週期
(手機橫屏看原始碼更方便)
注: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中分析。
彩蛋
本章我們一起從狀態定義、流程圖、原始碼分析等多個角度一起學習了執行緒池的生命週期,你掌握的怎麼樣呢?
下一章我們將開始學習執行緒池執行任務的主流程,對這一塊內容感到恐懼的同學可以先看看彤哥之前寫的“手寫執行緒池”的兩篇文章,對接下來學習執行緒池的主要流程非常有好處。
歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。
相關推薦
死磕 java執行緒系列之執行緒池深入解析——生命週期
(手機橫屏看原始碼更方便) 注:java原始碼分析部分如無特殊說明均基於 java8 版本。 注:執行緒池原始碼部分如無特殊說明均指ThreadPoolExecutor類。 簡介 上一章我們一起重溫了下執行緒的生命週期(六種狀態還記得不?),但是你知不知道其實執行緒池也是有生命週期的呢?! 問題 (1)
死磕java concurrent包系列(六)基於AQS解析訊號量Semaphore
Semaphore 之前分析AQS的時候,內部有兩種模式,獨佔模式和共享模式,前面的ReentrantLock都是使用獨佔模式,而Semaphore同樣作為一個基於AQS實現的併發元件,它是基於共享模式實現的,我們先看看它的使用場景 Semaphore共享鎖的基本使用 假設有20個人去銀行櫃面辦理業務,
死磕 java執行緒系列之執行緒模型
(2)執行緒模型有哪些? (3)各語言使用的是哪種執行緒模型? 簡介 在Java中,我們平時所說的併發程式設計、多執行緒、共享資源等概念都是與執行緒相關的,這裡所說的執行緒實際上應該叫作“使用者執行緒”,而對應到作業系統,還有另外一種執行緒叫作“核心執行緒”。 使用者執行緒位於核心之上,它的管理無需核心支援
死磕 java執行緒系列之執行緒池深入解析——體系結構
(手機橫屏看原始碼更方便) 注:java原始碼分析部分如無特殊說明均基於 java8 版本。 簡介 Java的執行緒池是塊硬骨頭,對執行緒池的原始碼做深入研究不僅能提高對Java整個併發程式設計的理解,也能提高自己在面試中的表現,增加被錄取的可能性。 本系列將分成很多個章節,本章作為執行緒池的第一章將對
死磕 java執行緒系列之執行緒池深入解析——普通任務執行流程
(手機橫屏看原始碼更方便) 注:java原始碼分析部分如無特殊說明均基於 java8 版本。 注:執行緒池原始碼部分如無特殊說明均指ThreadPoolExecutor類。 簡介 前面我們一起學習了Java中執行緒池的體系結構、構造方法和生命週期,本章我們一起來學習執行緒池中普通任務到底是怎麼執行的。
死磕 java執行緒系列之執行緒池深入解析——未來任務執行流程
(手機橫屏看原始碼更方便) 注:java原始碼分析部分如無特殊說明均基於 java8 版本。 注:執行緒池原始碼部分如無特殊說明均指ThreadPoolExecutor類。 簡介 前面我們一起學習了執行緒池中普通任務的執行流程,但其實執行緒池中還有一種任務,叫作未來任務(future task),使用它
死磕 java執行緒系列之執行緒池深入解析——定時任務執行流程
(手機橫屏看原始碼更方便) 注:java原始碼分析部分如無特殊說明均基於 java8 版本。 注:本文基於ScheduledThreadPoolExecutor定時執行緒池類。 簡介 前面我們一起學習了普通任務、未來任務的執行流程,今天我們再來學習一種新的任務——定時任務。 定時任務是我們經常會用到的一
死磕java concurrent包系列(一)從樂觀鎖、悲觀鎖到AtomicInteger的CAS演算法
前言 Java中有各式各樣的鎖,主流的鎖和概念如下: 這篇文章主要是為了讓大家通過樂觀鎖和悲觀鎖出發,理解CAS演算法,因為CAS是整個Concurrent包的基礎。 樂觀鎖和悲觀鎖 首先,java和資料庫中都有這種概念,他是一種從執行緒同步的角度上看的一種廣義上的概念: 悲觀鎖:悲觀的認為自己在使用資料的
死磕java concurrent包系列(三)基於ReentrantLock理解AQS的條件佇列
基於Codition分析AQS的條件佇列 前言 上一篇我們講了AQS中的同步佇列佇列,現在我們研究一下條件佇列。 在java中最常見的加鎖方式就是synchorinzed和Reentrantlock,我們都說Reentrantlock比synchorinzed更加靈活,其實就靈活在Reentrantlock中
死磕java concurrent包系列(五)基於AQS的條件佇列把LinkedBlockingQueue“扒光”
LinkedBlockingQueue的基礎 LinkedBlockingQueue是一個基於連結串列的阻塞佇列,實際使用上與ArrayBlockingQueue完全一樣,我們只需要把之前烤雞的例子中的Queue物件替換一下即可。如果對於ArrayBlockingQueue不熟悉,可以去看看https://
死磕 java執行緒系列之建立執行緒的8種方式
問題 (1)建立執行緒有哪幾種方式? (2)它們分別有什麼運用場景? 簡介 建立執行緒,是多執行緒程式設計中最基本的操作,彤哥總結了一下,大概有8種建立執行緒的方式,你知道嗎? 繼承Thread類並重寫run()方法 public class CreatingThread01 extends Thread
死磕 java執行緒系列之自己動手寫一個執行緒池
歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。 (手機橫屏看原始碼更方便) 問題 (1)自己動手寫一個執行緒池需要考慮哪些因素? (2)自己動手寫的執行緒池如何測試? 簡介 執行緒池是Java併發程式設計中經常使用到的技術,那麼自己如何動手寫一個執行緒池呢?本
死磕 java執行緒系列之自己動手寫一個執行緒池(續)
(手機橫屏看原始碼更方便) 問題 (1)自己動手寫的執行緒池如何支援帶返回值的任務呢? (2)如果任務執行的過程中丟擲異常了該
死磕 java執行緒系列之終篇
(手機橫屏看原始碼更方便) 簡介 執行緒系列我們基本就學完了,這一個系列我們基本都是圍繞著執行緒池在講,其實關於執行緒還有很多東西可以講,後面有機會我們再補充進來。當然,如果你有什麼好的想法,也可以公從號右下角聯絡我。 重要知識點 直接上圖,看著這張圖我相信你能夠回憶起很多東西,也可以看著這張圖來自己提
【死磕Java併發】-----J.U.C之AQS:阻塞和喚醒執行緒
此篇部落格所有原始碼均來自JDK 1.8 線上程獲取同步狀態時如果獲取失敗,則加入CLH同步佇列,通過通過自旋的方式不斷獲取同步狀態,但是在自旋的過程中則需要判斷當前執行緒是否需要阻塞,其主要方法在acquireQueued(): if (sho
【紮實基本功】Java基礎教程系列之多執行緒
1. 多執行緒的概念 1.1 程序、執行緒、多程序的概念 程序:正在進行中的程式(直譯)。 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒。 一個應用程式可以理解成就是一個程序。 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作。 1.
Java多執行緒系列--“JUC執行緒池”01之 執行緒池架構
概要 前面分別介紹了”Java多執行緒基礎”、”JUC原子類”和”JUC鎖”。本章介紹JUC的最後一部分的內容——執行緒池。內容包括: 執行緒池架構圖 執行緒池示例 執行緒池架構圖 執行緒池的架構圖如下: 1、Executor
Java多執行緒系列--“JUC執行緒池”05之 執行緒池原理(四)
概要 本章介紹執行緒池的拒絕策略。內容包括: 拒絕策略介紹 拒絕策略對比和示例 拒絕策略介紹 執行緒池的拒絕策略,是指當任務新增到執行緒池中被拒絕,而採取的處理措施。 當任務新增到執行緒池中之所以被拒絕,可能是由於:第一,執行緒池異常關閉。第二,任務數量
Java 併發程式設計系列之帶你瞭解多執行緒
早期的計算機不包含作業系統,它們從頭到尾執行一個程式,這個程式可以訪問計算機中的所有資源。在這種情況下,每次都只能執行一個程式,對於昂貴的計算機資源來說是一種嚴重的浪費。 作業系統出現後,計算機可以執行多個程式,不同的程式在單獨的程序中執行。作業系統負責為各個獨
java多執行緒系列之模式|第一篇-Guarded Suspension pattern
Guarded Suspension pattern模式 作者注:該系列文章基於《java執行緒設計模式》撰寫,只用於學習和交流。 定義:多執行緒執行,當前執行緒沒有達到警戒條件時,執行緒會進入等待直到