1. 程式人生 > >死磕 java執行緒系列之執行緒池深入解析——生命週期

死磕 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.CAQS:阻塞和喚醒執行

此篇部落格所有原始碼均來自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執行緒設計模式》撰寫,只用於學習和交流。 定義:多執行緒執行,當前執行緒沒有達到警戒條件時,執行緒會進入等待直到