聊聊 Java 多線程(5)- 超詳細(xì)的ThreadPoolExecutor源碼解析

目前,多線程編程可以說是在大部分平臺和應(yīng)用上都需要實現(xiàn)的一個基本需求。本系列文章就來對 Java 平臺下的多線程編程知識進(jìn)行講解,從概念入門、底層實現(xiàn)上層應(yīng)用都會涉及到,預(yù)計一共會有五篇文章,希望對你有所幫助 ????

本篇文章是第五篇,應(yīng)該也是最后一篇了,從現(xiàn)實需求出發(fā)到源碼介紹,一步步理清楚線程池的作用和優(yōu)勢

線程池(ThreadPool)面對的是外部復(fù)雜多變的多線程環(huán)境,既需要保證多線程環(huán)境下的狀態(tài)同步,也需要最大化對每個線程的利用率,還需要留給子類足夠多的余地來實現(xiàn)功能擴(kuò)展。所以說,線程池的難點(diǎn)在于如何實現(xiàn),而在概念上其實還是挺簡單的。在 Java 中,線程池這個概念一般都認(rèn)為對應(yīng)的是 JDK 中的 ThreadPoolExecutor 類及其各種衍生類,本篇文章就從實現(xiàn)思路出發(fā),探索 ThreadPoolExecutor 的源碼到底是如何實現(xiàn)的以及為什么這么實現(xiàn)

一、線程池

線程是一種昂貴的系統(tǒng)資源,其“昂貴”不僅在于創(chuàng)建線程所需要的資源開銷,還在于使用過程中帶來的資源消耗。一個系統(tǒng)能夠支持同時運(yùn)行的線程總數(shù)受限于該系統(tǒng)所擁有的處理器數(shù)目和內(nèi)存大小等硬件條件,線程的運(yùn)行需要占用處理器時間片,系統(tǒng)中處于運(yùn)行狀態(tài)的線程越多,每個線程單位時間內(nèi)能分配到的時間片就會越少,線程調(diào)度帶來的上下文切換的次數(shù)就會越多,最終導(dǎo)致處理器真正用于運(yùn)算的時間就會越少。此外,在現(xiàn)實場景中一個程序在其整個生命周期內(nèi)需要交由線程執(zhí)行的任務(wù)數(shù)量往往是遠(yuǎn)多于系統(tǒng)所能支持同時運(yùn)行的最大線程數(shù)?;谝陨显?,為每個任務(wù)都創(chuàng)建一個線程來負(fù)責(zé)執(zhí)行是不太現(xiàn)實的。那么,我們最直接的一個想法就是要考慮怎么來實現(xiàn)線程的復(fù)用了

線程池就是實現(xiàn)線程復(fù)用的一種有效方式。線程池的思想可以看做是對資源是有限的而需要處理的任務(wù)幾乎是無限的這樣一個現(xiàn)狀的應(yīng)對措施。線程池的一般實現(xiàn)思路是:線程池內(nèi)部預(yù)先創(chuàng)建或者是先后創(chuàng)建一定數(shù)量的線程,外部將需要執(zhí)行的任務(wù)作為一個對象提交給線程池,由線程池選擇某條空閑線程來負(fù)責(zé)執(zhí)行。如果所有線程都處于工作狀態(tài)且線程總數(shù)已經(jīng)達(dá)到限制條件了,則先將任務(wù)緩存到任務(wù)隊列中,線程再不斷從任務(wù)隊列中取出任務(wù)并執(zhí)行。因此,線程池可以看做是基于生產(chǎn)者-消費(fèi)者模式的一種服務(wù),內(nèi)部維護(hù)的多個線程相當(dāng)于消費(fèi)者,提交的任務(wù)相當(dāng)于產(chǎn)品,提交任務(wù)的外部就相當(dāng)于生產(chǎn)者

二、思考下

好了,既然已經(jīng)對線程池這個概念有了基本的了解,那么就再來思考下線程池應(yīng)該具備的功能以及應(yīng)該如何來實現(xiàn)線程池

  1. 線程池中的線程最大數(shù)量應(yīng)該如何限定?

既然我們不可能無限制地創(chuàng)建線程,那么在創(chuàng)建線程池前就需要為其設(shè)定一個最大數(shù)量,我們稱之為最大線程池大小(maximumPoolSize),當(dāng)線程池中的當(dāng)前線程總數(shù)達(dá)到 maximumPoolSize 后就不應(yīng)該再創(chuàng)建線程了。在開發(fā)中,我們需要根據(jù)運(yùn)行設(shè)備的硬件條件和任務(wù)類型(I/O 密集型或者 CPU 密集型)來實際衡量該數(shù)值的大小,但任務(wù)的提交頻率和任務(wù)的所需執(zhí)行時間是不固定的,所以線程池的 maximumPoolSize 也應(yīng)該支持動態(tài)調(diào)整

  1. 線程池中的線程應(yīng)該在什么時候被創(chuàng)建呢?

一般來說,如果線程池中的線程數(shù)量還沒有達(dá)到 maximumPoolSize 時,我們可以等到當(dāng)外部提交了任務(wù)時再來創(chuàng)建線程進(jìn)行處理。但是,線程從被創(chuàng)建到被調(diào)度器選中運(yùn)行,之間也是有著一定時間間隔的。從提高任務(wù)的處理響應(yīng)速度這方面考慮,我們也可以選擇預(yù)先就創(chuàng)建一批線程進(jìn)行等待

  1. 線程池中的線程可以一直存活著嗎?

程序運(yùn)行過程中可能只是偶發(fā)性地大批量提交任務(wù),而大部分時間只是比較零散地提交少量任務(wù),這就導(dǎo)致線程池中的線程可能會在一段時間內(nèi)處于空閑狀態(tài)。如果線程池中的線程只要創(chuàng)建了就可以一直存活著的話,那么線程池的“性價比”就顯得沒那么高了。所以,當(dāng)線程處于空閑狀態(tài)的時間超出允許的最大空閑時間(keepAliveTime)后,我們就應(yīng)該將其回收,避免白白浪費(fèi)系統(tǒng)資源。而又為了避免頻繁地創(chuàng)建和銷毀線程,線程池需要緩存一定數(shù)量的線程,即使其處于空閑狀態(tài)也不會進(jìn)行回收,這類線程我們就稱之為核心線程,相應(yīng)的線程數(shù)量就稱之為核心線程池大?。╟orePoolSize)。大于 corePoolSize 而小于等于 maximumPoolSize 的那一部分線程,就稱之為非核心線程

  1. 如何實現(xiàn)線程的復(fù)用?

我們知道,當(dāng) Thread.run() 方法執(zhí)行結(jié)束后線程就會被回收了,那么想要實現(xiàn)線程的復(fù)用,那么就要考慮如何避免退出 Thread.run() 了。這里,我們可以通過循環(huán)向任務(wù)隊列取值的方式來實現(xiàn)。上面有提到,如果外部提交的任務(wù)過多,那么任務(wù)就需要被緩存到任務(wù)隊列中。那么,我們就可以考慮使用一個阻塞隊列來存放任務(wù)。線程循環(huán)從任務(wù)隊列中取任務(wù),如果隊列不為空,那么就可以馬上拿到任務(wù)進(jìn)行執(zhí)行;如果隊列為空,那么就讓線程一直阻塞等待,直到外部提交了任務(wù)被該線程拿到或者由于超時退出循環(huán)。通過這種循環(huán)獲取+阻塞等待的方式,就可以實現(xiàn)線程復(fù)用的目的

  1. 如何盡量實現(xiàn)線程的復(fù)用?

這個問題和“如何實現(xiàn)線程的復(fù)用”不太一樣,“如何實現(xiàn)線程的復(fù)用”針對的是單個線程的復(fù)用流程,本問題針對的是整個線程池范圍的復(fù)用。線程池中需要使用到任務(wù)隊列進(jìn)行緩存,那么任務(wù)隊列的使用時機(jī)可以有以下幾種:

  • 當(dāng)線程數(shù)已經(jīng)達(dá)到 maximumPoolSize ,且所有線程均處于工作狀態(tài)時,此后外部提交的任務(wù)才被緩存到任務(wù)隊列中
  • 當(dāng)核心線程都已經(jīng)被創(chuàng)建了時,此后外部提交的任務(wù)就被緩存到任務(wù)隊列中,當(dāng)任務(wù)隊列滿了后才創(chuàng)建非核心線程來循環(huán)處理任務(wù)

很明顯的,第二種方案會更加優(yōu)秀。由于核心線程一般情況下是會被長久保留的,核心線程的存在保證了外部提交的任務(wù)一直有在被循環(huán)處理。如果外部提交的大部分都是耗時較短的任務(wù)或者任務(wù)的提交頻率比較低的話,那么任務(wù)隊列就可能沒那么容易滿,第二種方案就可以盡量避免去創(chuàng)建非核心線程。而且對于“偶發(fā)性地大批量提交任務(wù),而大部分時間只是比較零散地提交少量任務(wù)”這種情況,第二種方案也會更加合適。當(dāng)然,在任務(wù)的處理速度方面,第一種方案就會高一些,但是如果想要盡量提高第二種方案的任務(wù)處理速度的話,也可以通過將任務(wù)隊列的容量調(diào)小的方式來實現(xiàn)

  1. 當(dāng)任務(wù)隊列滿了后該如何處理?

如果線程池實在“忙不過來”的話,那么任務(wù)隊列也是有可能滿的,那么就需要為這種情況指定處理策略。當(dāng)然,我們也可以選擇使用一個無界隊列來緩存任務(wù),但是無界隊列容易掩蓋掉一些程序異常。因為有界隊列之所以會滿,可能是由于發(fā)生線程池死鎖或者依賴的某個基礎(chǔ)服務(wù)失效導(dǎo)致的,從而令線程池中的任務(wù)一直遲遲得不到解決。如果使用的是無界隊列的話,就可能使得當(dāng)系統(tǒng)發(fā)生異常時程序還是看起來運(yùn)轉(zhuǎn)正常,從而降低了系統(tǒng)健壯性。所以,最常用的還是有界隊列

現(xiàn)實需求是多樣化的,在實現(xiàn)線程池時就需要留有交由外部自定義處理策略的余地。例如,當(dāng)隊列滿了后,我們可以選擇直接拋出異常來向外部“告知”這一異常情況。對于重要程度較低的任務(wù),可以選擇直接拋棄該任務(wù),也可以選擇拋棄隊列頭的任務(wù)而嘗試接納新到來的任務(wù)。如果任務(wù)必須被執(zhí)行的話,也可以直接就在提交任務(wù)的線程上進(jìn)行執(zhí)行

以上就是線程池在實現(xiàn)過程中需要主要考慮的幾個點(diǎn),下面就來看下 Java 實際上是怎么實現(xiàn)線程池的

三、ThreadPoolExecutor

java.util.concurrent.ThreadPoolExecutor 類就是 Java 對線程池的默認(rèn)實現(xiàn),下文如果沒有特別說明的話,所說的線程池就是指 ThreadPoolExecutor

ThreadPoolExecutor 的繼承關(guān)系如下圖所示

ThreadPoolExecutor 實現(xiàn)的最頂層接口是 Executor。Executor 提供了一種將任務(wù)的提交任務(wù)的執(zhí)行兩個操作進(jìn)行解耦的思路:客戶端無需關(guān)注執(zhí)行任務(wù)的線程是如何創(chuàng)建、運(yùn)行和回收的,只需要將任務(wù)的執(zhí)行邏輯包裝為一個 Runnable 對象傳遞進(jìn)來即可,由 Executor 的實現(xiàn)類自己來完成最復(fù)雜的執(zhí)行邏輯

ExecutorService 接口在 Executor 的基礎(chǔ)上擴(kuò)展了一些功能:

  1. 擴(kuò)展執(zhí)行任務(wù)的能力。例如:獲取任務(wù)的執(zhí)行結(jié)果、取消任務(wù)等功能
  2. 提供了關(guān)閉線程池、停止線程池,以及阻塞等待線程池完全終止的方法

AbstractExecutorService 則是上層的抽象類,負(fù)責(zé)將任務(wù)的執(zhí)行流程串聯(lián)起來,從而使得下層的實現(xiàn)類 ThreadPoolExecutor 只需要實現(xiàn)一個執(zhí)行任務(wù)的方法即可

也正如上文所說的那樣,ThreadPoolExecutor 可以看做是基于生產(chǎn)者-消費(fèi)者模式的一種服務(wù),內(nèi)部維護(hù)的多個線程相當(dāng)于消費(fèi)者,提交的任務(wù)相當(dāng)于產(chǎn)品,提交任務(wù)的外部就相當(dāng)于生產(chǎn)者。其整個運(yùn)行流程如下圖所示

而在線程池的整個生命周期中,以下三個關(guān)于線程數(shù)量的統(tǒng)計結(jié)果也影響著線程池的流程走向

  1. 當(dāng)前線程池大?。╟urrentPoolSize)。表示當(dāng)前實時狀態(tài)下線程池中線程的數(shù)量
  2. 最大線程池大小(maximumPoolSize)。表示線程池中允許存在的線程的數(shù)量上限
  3. 核心線程池大?。╟orePoolSize)。表示一個不大于 maximumPoolSize 的線程數(shù)量上限

三者之間的關(guān)系如下:

當(dāng)前線程池大小  ≤  核心線程池大小  ≤  最大線程池大小
or
核心線程池大小  ≤  當(dāng)前線程池大小  ≤  最大線程池大小

當(dāng)中,除了“當(dāng)前線程池大小”是對線程池現(xiàn)有的工作者線程進(jìn)行實時計數(shù)的結(jié)果,其它兩個值都是對線程池配置的參數(shù)值。三個值的作用在上文也都已經(jīng)介紹了

ThreadPoolExecutor 中參數(shù)最多的一個構(gòu)造函數(shù)的聲明如下所示:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize :用于指定線程池的核心線程數(shù)大小
  • maximumPoolSize:用于指定最大線程池大小
  • keepAliveTime、unit :一起用于指定線程池中空閑線程的最大存活時間
  • workQueue :任務(wù)隊列,相當(dāng)于生產(chǎn)者-消費(fèi)者模式中的傳輸管道,用于存放待處理的任務(wù)
  • threadFactory:用于指定創(chuàng)建線程的線程工廠
  • handler:用于指定當(dāng)任務(wù)隊列已滿且線程數(shù)量達(dá)到 maximumPoolSize 時任務(wù)的處理策略

下面就以點(diǎn)見面,從細(xì)節(jié)處來把握整個線程池的流程走向

1、線程池的狀態(tài)如何保存

這里所說的“狀態(tài)”指的是一個復(fù)合數(shù)據(jù),包含“線程池生命周期狀態(tài)”和“線程池當(dāng)前線程數(shù)量”這兩項。線程池從啟動到最終終止,其內(nèi)部需要記錄其當(dāng)前狀態(tài)來決定流程走向。而線程池的當(dāng)前線程數(shù)量,也關(guān)乎著線程是否需要進(jìn)行回收以及是否需要執(zhí)行任務(wù)的拒絕策略

線程池一共包含以下五種生命周期狀態(tài),涵蓋了線程池從啟動到終止的這整個范圍。線程池的生命周期狀態(tài)可以按順序躍遷,但無法反向回轉(zhuǎn),每個狀態(tài)的數(shù)值大小也是逐步遞增的

//運(yùn)行狀態(tài),線程池正處于運(yùn)行中
private static final int RUNNING    = -1 << COUNT_BITS;
//關(guān)閉狀態(tài),當(dāng)調(diào)用 shutdown() 方法后處于這個狀態(tài),任務(wù)隊列中的任務(wù)會繼續(xù)處理,但不再接受新任務(wù),
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//停止?fàn)顟B(tài),當(dāng)調(diào)用 shutdownNow() 方法后處于這個狀態(tài)
//任務(wù)隊列中的任務(wù)也不再處理且作為方法返回值返回,此后不再接受新任務(wù)
private static final int STOP       =  1 << COUNT_BITS;
//TERMINATED 之前的臨時狀態(tài),當(dāng)線程都被回收且任務(wù)隊列已清空后就會處于這個狀態(tài)
private static final int TIDYING    =  2 << COUNT_BITS;
//終止?fàn)顟B(tài),在處于 TIDYING 狀態(tài)后會立即調(diào)用 terminated() 方法,調(diào)用完成就會馬上轉(zhuǎn)到此狀態(tài)
private static final int TERMINATED =  3 << COUNT_BITS;

在日常開發(fā)中,如果我們想要用一個 int 類型的 state 變量來表示這五種狀態(tài)的話,那么就可能是通過讓 state 分別取值 1,2,3,4,5 來進(jìn)行標(biāo)識,而 state 作為一個 int 類型是一共有三十二位的,那其實上僅需要占用三位就足夠標(biāo)識了,即 2 x 2 x 2 = 8 種可能。那還剩下 29 位可以用來存放其它數(shù)據(jù)

實際上 ThreadPoolExecutor 就是通過將一個 32 位的 int 類型變量分割為兩段,高 3 位用來表示線程池的當(dāng)前生命周期狀態(tài),低 29 位就拿來表示線程池的當(dāng)前線程數(shù)量,從而做到用一個變量值來維護(hù)兩份數(shù)據(jù),這個變量值就是 ctl。從 ctl 的初始值就可以知道線程池的初始生命周期狀態(tài)( runState )是 RUNNING,初始線程數(shù)量 ( workerCount )是 0。這種用一個變量去存儲兩個值的做法,可以避免在做相關(guān)決策時出現(xiàn)不一致的情況,且不必為了維護(hù)兩者的一致而使用鎖,后續(xù)需要獲取線程池的當(dāng)前生命周期狀態(tài)線程數(shù)量的時候,也可以直接采用位運(yùn)算的方式獲取,在速度上相比基本運(yùn)算會快很多

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ThreadPoolExecutor 還聲明了以下兩個常量用來參與位運(yùn)算

//用來表示線程數(shù)量的位數(shù),即 29
private static final int COUNT_BITS = Integer.SIZE - 3;
//線程池所能表達(dá)的最大線程數(shù),即一個“高3位全是0,低29位全是1”的數(shù)值
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

相應(yīng)的,那么就需要有幾個方法可以來分別取“生命周期狀態(tài)”和“線程數(shù)”這兩個值,以及將這兩個值合并保存的方法,這幾個方法都使用到了位運(yùn)算

// Packing and unpacking ctl

//通過按位取反 + 按位與運(yùn)算,將 c 的高3位保留,舍棄低29位,從而得到線程池狀態(tài)
private static int runStateOf(int c)     { return c & ~CAPACITY; }

//通過按位與運(yùn)算,將 c 的高3位舍棄,保留低29位,從而得到工作線程數(shù)
private static int workerCountOf(int c)  { return c & CAPACITY; }

//rs,即 runState,線程池的生命周期狀態(tài)
//wc,即 workerCount,工作線程數(shù)量
//通過按位或運(yùn)算來合并值
private static int ctlOf(int rs, int wc) { return rs | wc; }

private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

//用于判斷線程池是否處于 RUNNING 狀態(tài)
//由于五個狀態(tài)值的大小是依次遞增的,所以只需要和 SHUTDOWN 比較即可
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

public boolean isShutdown() {
    return !isRunning(ctl.get());
}

//用于判斷當(dāng)前狀態(tài)是否處于 SHUTDOWN、STOP、TIDYING 三者中的一個
public boolean isTerminating() {
    int c = ctl.get();
    return !isRunning(c) && runStateLessThan(c, TERMINATED);
}

//用于判斷當(dāng)前狀態(tài)是否處于 TERMINATED
public boolean isTerminated() {
    return runStateAtLeast(ctl.get(), TERMINATED);
}

2、線程的創(chuàng)建流程

在初始狀態(tài)下,客戶端每提交一個任務(wù),線程池就會通過 threadFactory 創(chuàng)建線程來處理該任務(wù),如果開發(fā)者沒有指定 threadFactory 的話,則會使用 Executors.DefaultThreadFactory。線程池在最先開始創(chuàng)建的線程屬于核心線程,線程數(shù)量在大于 corePoolSize 而小于等于 maximumPoolSize 這個范圍內(nèi)的線程屬于非核心線程。需要注意的是,核心線程和非核心線程并非是兩種不同類型的線程對象,這兩個概念只是對不同數(shù)量范圍內(nèi)的線程進(jìn)行的區(qū)分,實質(zhì)上這兩者指向的都是同一類型

線程的創(chuàng)建流程可以通過任務(wù)的提交流程來了解,任務(wù)的提交流程圖如下所示

線程池開放了多個讓外部提交任務(wù)的方法,這里主要看 execute(Runnable command) 方法。該方法需要先后多次校驗狀態(tài)值,因為線程池面對的調(diào)用方可以來自于多個不同的線程??赡茉诋?dāng)前線程提交任務(wù)的同時,其它線程就剛好關(guān)閉了線程池或者是調(diào)整了線程池的線程大小參數(shù),需要考慮當(dāng)前的線程數(shù)量是否已經(jīng)達(dá)到限制了

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        //如果當(dāng)前線程數(shù)還未達(dá)到 corePoolSize,則嘗試創(chuàng)建一個核心線程來處理任務(wù)
        //addWorker 可能會因為線程池被關(guān)閉了、線程數(shù)量超出限制等原因返回 false
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    if (isRunning(c) && workQueue.offer(command)) { //線程池還處于運(yùn)行狀態(tài)且成功添加任務(wù)到任務(wù)隊列
        //需要重新檢查下運(yùn)行狀態(tài)
        //因為等執(zhí)行到這里時,線程池可能被其它線程關(guān)閉了
        int recheck = ctl.get();

        //1、如果線程池已經(jīng)處于非運(yùn)行狀態(tài)了
        //1.1、如果移除 command 成功,則走拒絕策略
        //1.2、如果移除 command 失?。ㄒ驗?command 可能已經(jīng)被其它線程拿去執(zhí)行了),則走第 3 步
        //2、如果線程池還處于運(yùn)行狀態(tài),則走第 3 步
        //3、如果當(dāng)前線程數(shù)量為 0,則創(chuàng)建線程進(jìn)行處理
        //第 3 步的意義在于:corePoolSize 可以被設(shè)為 0,所以這里需要檢查下,在需要的時候創(chuàng)建一個非核心線程
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果線程池處于非運(yùn)行狀態(tài)了,或者是處于運(yùn)行狀態(tài)但隊列已滿了,此時就會走到這里
    //在這里嘗試創(chuàng)建一個非核心線程
    //如果線程創(chuàng)建失敗,說明要么是線程池當(dāng)前狀態(tài)大于等于 STOP,或者是任務(wù)隊列已滿且線程總數(shù)達(dá)到 maximumPoolSize 了
    //此時就走拒絕策略
    else if (!addWorker(command, false))
        reject(command);
}

當(dāng)中,addWorker(Runnable firstTask, boolean core) 方法用于嘗試創(chuàng)建并啟動線程,同時將線程保存到 workers。第一個參數(shù)用于指定線程啟動時需要執(zhí)行的第一個任務(wù),可以為 null。第二個參數(shù)用于指定要創(chuàng)建的是否是核心線程,這個參數(shù)會關(guān)系到線程是否能被成功創(chuàng)建

該方法在實際創(chuàng)建線程前,都需要先通過 CAS 來更新(遞增加一)當(dāng)前的線程總數(shù),通過 for 循環(huán)來不斷進(jìn)行重試。當(dāng) CAS 成功后,則會再來實際進(jìn)行線程的創(chuàng)建操作。但在這時候線程也未必能夠創(chuàng)建成功,因為在 CAS 成功后線程池可能被關(guān)閉了,或者是在創(chuàng)建線程時拋出異常了,此時就需要回滾對 workerCount 的修改

該方法如果返回 true,意味著新創(chuàng)建了一個 Worker 線程,同時線程也被啟動了

該方法如果返回 false,則可能是由于以下情況:

  1. 生命周期狀態(tài)大于等于 STOP
  2. 生命周期狀態(tài)等于 SHUTDOWN,但 firstTask 不為 null,或者任務(wù)隊列為空
  3. 當(dāng)前線程數(shù)已經(jīng)超出限制
  4. 符合創(chuàng)建線程的條件,但創(chuàng)建過程中或啟動線程的過程中拋出了異常
private boolean addWorker(Runnable firstTask, boolean core) {
    //下面的 for 主要邏輯:
    //在創(chuàng)建線程前通過 CAS 原子性地將“工作者線程數(shù)量遞增加一”
    //由于 CAS 可能會失敗,所以將之放到 for 循環(huán)中進(jìn)行循環(huán)重試
    //每次循環(huán)前后都需要檢查下當(dāng)前狀態(tài)是否允許創(chuàng)建線程
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))   
            //當(dāng)外部調(diào)用 shutdown() 方法后,線程池狀態(tài)會變遷為 SHUTDOWN
            //此時依然允許創(chuàng)建線程來對隊列中的任務(wù)進(jìn)行處理,但是不會再接受新任務(wù)
            //除這種情況之外不允許在非 RUNNING 的時候還創(chuàng)建線程
            return false;

        for (;;) {  
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                //當(dāng)前線程數(shù)已經(jīng)超出最大限制
                return false;
            if (compareAndIncrementWorkerCount(c))
                //通過 CAS 更新工作者線程數(shù)成功后就跳出循環(huán),去實際創(chuàng)建線程
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                //循環(huán)過程中線程池狀態(tài)被改變了,重新循環(huán)
                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) {
            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);
                    //更新線程池曾經(jīng)達(dá)到的最大線程數(shù)
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //如果線程沒有被成功啟動,則需要將該任務(wù)從隊列中移除并重新更新工作者線程數(shù)
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

3、線程的執(zhí)行流程

上面所講的線程其實指的是 ThreadPoolExecutor 的內(nèi)部類 Worker ,Worker 內(nèi)部包含了一個 Thread 對象,所以本文就把 Worker 實例也看做線程來對待

Worker 繼承于 AbstractQueuedSynchronizer,意味著 Worker 就相當(dāng)于一個鎖。之沒有使用 synchronized 或者 ReentrantLock,是因為它們都是可重入鎖,Worker 繼承于 AQS 為的就是自定義實現(xiàn)不可重入的特性來輔助判斷線程是否處于執(zhí)行任務(wù)的狀態(tài):在開始執(zhí)行任務(wù)前進(jìn)行加鎖,在任務(wù)執(zhí)行結(jié)束后解鎖,以便在后續(xù)通過判斷 Worker 是否處于鎖定狀態(tài)來得知其是否處于執(zhí)行階段

  1. Worker 在開始執(zhí)行任務(wù)前會執(zhí)行 Worker.lock() ,表明線程正在執(zhí)行任務(wù)
  2. 如果 Worker 處于鎖定狀態(tài),則不應(yīng)該對其進(jìn)行中斷,避免任務(wù)執(zhí)行一半就被打斷
  3. 如果 Worker 處于非鎖定狀態(tài),說明其當(dāng)前是處于阻塞獲取任務(wù)的狀態(tài),此時才允許對其進(jìn)行中斷
  4. 線程池在執(zhí)行 shutdown() 方法或 shutdownNow() 方法時會調(diào)用 interruptIdleWorkers() 方法來回收空閑的線程,interruptIdleWorkers() 方法會使用Worker.tryLock() 方法來嘗試獲取鎖,由于 Worker 是不可重入鎖,所以如果鎖獲取成功就說明線程處于空閑狀態(tài),此時才可以進(jìn)行回收

Worker 同時也是 Runnable 類型,thread 是通過 getThreadFactory().newThread(this) 來創(chuàng)建的,即將 Worker 本身作為構(gòu)造參數(shù)傳給 Thread 進(jìn)行初始化,所以在 thread 啟動的時候 Worker 的 run() 方法就會被執(zhí)行

private final class Worker extends AbstractQueuedSynchronizer implements Runnable  {
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;

    //線程要執(zhí)行的第一個任務(wù),可能為 null
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;

    //用于標(biāo)記 Worker 執(zhí)行過的任務(wù)數(shù)(不管成功與否都記錄)
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    //只有在 state 值為 0 的時候才能獲取到鎖,以此實現(xiàn)不可重入的特性
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    //向線程發(fā)起中斷請求
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

runWorker(Worker) 方法就是線程正式進(jìn)行任務(wù)執(zhí)行的地方。該方法通過 while 循環(huán)不斷從任務(wù)隊列中取出任務(wù)來進(jìn)行執(zhí)行,如果 getTask()方法返回了 null,那此時就需要將此線程進(jìn)行回收。如果在任務(wù)執(zhí)行過程中拋出了異常,那也需要回收此線程

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;

    //因為 Worker 的默認(rèn)值是 -1,而 Worker 的 interruptIfStarted() 方法只有在 state >=0 的時候才允許進(jìn)行中斷
    //所以這里調(diào)用 unlock() 并不是為了解鎖,而是為了讓 Worker 的 state 值變?yōu)?0,讓 Worker 允許外部進(jìn)行中斷
    //所以,即使客戶端調(diào)用了 shutdown 或者 shutdownNow 方法,在 Worker 線程還未執(zhí)行到這里前,無法在 interruptWorkers() 方法里發(fā)起中斷請求
    w.unlock(); // allow interrupts

    //用于標(biāo)記是否由于被打斷而非正常結(jié)束導(dǎo)致的線程終止
    //為 true 表示非正常結(jié)束
    boolean completedAbruptly = true;

    try {
        // 如果 getTask() 為 null,說明線程池已經(jīng)被停止或者需要進(jìn)行線程回收
        while (task != null || (task = getTask()) != null) {

            //在開始執(zhí)行任務(wù)前進(jìn)行加鎖,在任務(wù)執(zhí)行結(jié)束后解鎖
            //以便在后續(xù)通過判斷 Worker 是否處于鎖定狀態(tài)來得知其是否處于執(zhí)行階段
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //確保當(dāng)狀態(tài)值大于等于 STOP 時有向線程發(fā)起過中斷請求
            if ((runStateAtLeast(ctl.get(), STOP) 
                ||
                (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) 
                &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //回收此線程
        processWorkerExit(w, completedAbruptly);
    }
}

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (; ; ) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //如何當(dāng)前狀態(tài)大于等于 STOP,則返回 null
        //如何當(dāng)前狀態(tài)是 SHUTDOWN 且任務(wù)隊列為空,則返回 null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //timed 用于標(biāo)記從任務(wù)隊列中取任務(wù)時是否需要進(jìn)行超時控制
        //如果允許回收空閑核心線程或者是當(dāng)前的線程總數(shù)已經(jīng)超出 corePoolSize 了,那么就需要進(jìn)行超時控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //1. 線程總數(shù)超出 maximumPoolSize
        //2. 允許回收核心線程,且核心線程的空閑時間已達(dá)到限制了
        //如果以上兩種情況之一有一個滿足,且當(dāng)前線程數(shù)大于 1 或者任務(wù)隊列為空時就返回 null(如果 CAS 更新 WorkerCount 成功的話)
        //避免在任務(wù)隊列不為空且只有一個線程時還回收線程導(dǎo)致任務(wù)沒人處理
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
            //如果 r 為 null,說明是由于超時導(dǎo)致 poll 返回了 null
            //在下一次循環(huán)時將判斷是否回收此線程
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

getTask() 方法獲取任務(wù)的流程圖如下所示:

4、線程的回收流程

當(dāng)外部調(diào)用了線程池的以下幾個方法之一時,就會觸發(fā)到線程的回收機(jī)制:

  1. 允許回收核心線程:allowCoreThreadTimeOut()
  2. 重置核心線程池大?。簊etCorePoolSize()
  3. 重置最大線程池大?。簊etMaximumPoolSize()
  4. 重置線程最大空閑時間:setKeepAliveTime()
  5. 關(guān)閉線程池:shutdown()
  6. 停止線程池:shutdownNow()
/**
 * 用于控制核心線程是否可以由于空閑時間超時而被回收
 * 超時時間和非核心線程一樣由 keepAliveTime 來指定
 *
 * @param value
 */
public void allowCoreThreadTimeOut(boolean value) {
    if (value && keepAliveTime <= 0)
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    if (value != allowCoreThreadTimeOut) {
        allowCoreThreadTimeOut = value;
        if (value)
            //回收掉空閑線程
            interruptIdleWorkers();
    }
}

/**
 * 重置 corePoolSize
 *
 * @param corePoolSize
 */
public void setCorePoolSize(int corePoolSize) {
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    int delta = corePoolSize - this.corePoolSize;
    this.corePoolSize = corePoolSize;
    if (workerCountOf(ctl.get()) > corePoolSize)
        //如果當(dāng)前的線程總數(shù)已經(jīng)超出新的 corePoolSize 的話那就進(jìn)行線程回收
        interruptIdleWorkers();
    else if (delta > 0) {
        //會走進(jìn)這里,說明新的 corePoolSize 比原先的大,但當(dāng)前線程總數(shù)還小于等于新的 corePoolSize
        //此時如果任務(wù)隊列不為空的話,那么就需要創(chuàng)建一批新的核心線程來處理任務(wù)
        //delta 和 workQueueSize 中的最小值就是需要啟動的線程數(shù)
        //而如果在創(chuàng)建過程中任務(wù)隊列已經(jīng)空了(被其它線程拿去處理了),那就不再創(chuàng)建線程
        int k = Math.min(delta, workQueue.size());
        while (k-- > 0 && addWorker(null, true)) {
            if (workQueue.isEmpty())
                break;
        }
    }
}

/**
 * 用于設(shè)置線程池允許存在的最大活躍線程數(shù)
 *
 * @param maximumPoolSize
 */
public void setMaximumPoolSize(int maximumPoolSize) {
    if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
        throw new IllegalArgumentException();
    this.maximumPoolSize = maximumPoolSize;
    if (workerCountOf(ctl.get()) > maximumPoolSize)
        //回收掉空閑線程
        interruptIdleWorkers();
}

/**
 * 用于設(shè)置非核心線程在空閑狀態(tài)能夠存活的時間
 *
 * @param time
 * @param unit
 */
public void setKeepAliveTime(long time, TimeUnit unit) {
    if (time < 0)
        throw new IllegalArgumentException();
    //為了避免頻繁創(chuàng)建線程,核心線程如果允許超時回收的話,超時時間不能為 0
    if (time == 0 && allowsCoreThreadTimeOut())
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    long keepAliveTime = unit.toNanos(time);
    long delta = keepAliveTime - this.keepAliveTime;
    this.keepAliveTime = keepAliveTime;
    if (delta < 0) //如果新設(shè)置的值比原先的超時時間小的話,那就需要去回收掉空閑線程
        interruptIdleWorkers();
}

 /**
 * 關(guān)閉線程池
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //將當(dāng)前狀態(tài)設(shè)置為 SHUTDOWN
        advanceRunState(SHUTDOWN);
        //回收掉空閑線程
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    //嘗試看是否能把線程池狀態(tài)置為 TERMINATED
    tryTerminate();
}

/**
 * 停止線程池
 *
 * @return 任務(wù)隊列中緩存的所有任務(wù)
 */
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //將當(dāng)前狀態(tài)設(shè)置為 STOP
        advanceRunState(STOP);
        //回收掉空閑線程
        interruptWorkers();
        //獲取任務(wù)隊列中緩存的所有任務(wù)
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    //嘗試看是否能把線程池狀態(tài)置為 TERMINATED
    tryTerminate();
    return tasks;
}

上述的幾個方法最終都會調(diào)用 interruptIdleWorkers(boolean onlyOne) 方法來回收空閑線程。該方法通過向線程發(fā)起中斷請求來使 Worker 退出 runWorker(Worker w) 方法,最終會調(diào)用 processWorkerExit(Worker w, boolean completedAbruptly) 方法來完成實際的線程回收操作

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            //僅在線程的中斷標(biāo)記為 false 時才發(fā)起中斷,避免重復(fù)發(fā)起中斷請求
            //且僅在 w.tryLock() 能成功(即 Worker 并非處于執(zhí)行任務(wù)的階段)才發(fā)起中斷,避免任務(wù)還未執(zhí)行完就被打斷
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

/**
 * 回收線程
 *
 * @param w                 Worker
 * @param completedAbruptly 是否是由于任務(wù)執(zhí)行過程拋出異常導(dǎo)致需要來回收線程
 *                          true:由于任務(wù)拋出異常
 *                          false:由于線程空閑時間達(dá)到限制條件
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //更新線程池總共處理過的任務(wù)數(shù)
        completedTaskCount += w.completedTasks;
        //移除此線程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        //在任務(wù)隊列不為空的時候,需要確保至少有一個線程可以來處理任務(wù),否則就還是需要再創(chuàng)建一個新線程
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && !workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

除了上述幾個方法會主動觸發(fā)到線程回收機(jī)制外,當(dāng)線程池滿足以下幾種情況之一時,也會進(jìn)行線程的回收:

  1. 非核心線程的空閑時間超出了 keepAliveTime
  2. allowCoreThreadTimeOut 為 true 且核心線程的空閑時間超出了 keepAliveTime

以上幾種情況其觸發(fā)時機(jī)主要看 getTask() 方法就可以。在向任務(wù)隊列 workQueue 獲取任務(wù)前,通過判斷當(dāng)前線程池的 allowCoreThreadTimeOut、corePoolSize、workerCount 等參數(shù)來決定是否需要對“從任務(wù)隊列獲取任務(wù)”這個操作進(jìn)行限時。如果需要進(jìn)行限時且獲取任務(wù)的時間超出 keepAliveTime 的話,那就說明此線程的空閑時間已經(jīng)達(dá)到限制了,需要對其進(jìn)行回收

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (; ; ) {
        int c = ctl.get();
        int rs = runStateOf(c);

        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //timed 用于標(biāo)記從任務(wù)隊列中取任務(wù)時是否需要進(jìn)行超時控制
        //如果允許回收空閑核心線程或者是當(dāng)前的線程總數(shù)已經(jīng)超出 corePoolSize 了,那么就需要進(jìn)行超時控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //1. 線程總數(shù)超出 maximumPoolSize
        //2. 允許回收核心線程,且核心線程的空閑時間已達(dá)到限制了
        //如果以上兩種情況之一有一個滿足,且當(dāng)前線程數(shù)大于 1 或者任務(wù)隊列為空時就返回 null(如果 CAS 更新 WorkerCount 成功的話)
        //避免在任務(wù)隊列不為空且只有一個線程時還回收線程導(dǎo)致任務(wù)沒人處理
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
            //如果能執(zhí)行到 timedOut = true 說明是由于超時導(dǎo)致 poll 返回了 null
            //之所以不在判斷到 r 不為 null 的時候就直接 return 出去
            //是因為可能在獲取任務(wù)的過程中外部又重新修改了 allowCoreThreadTimeOut 和 corePoolSize 等配置
            //導(dǎo)致此時又不需要回收此線程了,所以就在下一次循環(huán)時再判斷是否回收此線程
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

以上就是線程池基本所有的線程回收流程。線程回收機(jī)制有助于節(jié)約系統(tǒng)資源,但如果 corePoolSize、keepAliveTime 等參數(shù)設(shè)置得和系統(tǒng)的實際運(yùn)行情況不符的話,反而會導(dǎo)致線程頻繁地被創(chuàng)建和回收,反而加大了資源開銷

5、線程池的關(guān)閉流程

shutdown()shutdownNow() 方法可以用來關(guān)閉和停止線程池

  • shutdown()。使用該方法,已提交的任務(wù)會被繼續(xù)執(zhí)行,而后續(xù)新提交的任務(wù)則會走拒絕策略。該方法返回時,線程池可能尚未走向終止?fàn)顟B(tài) TERMINATED,即線程池中可能還有線程還在執(zhí)行任務(wù)
  • shutdownNow()。使用該方法,正在運(yùn)行的線程會嘗試停止,任務(wù)隊列中的任務(wù)也不會執(zhí)行而是作為方法返回值返回。由于該方法是通過調(diào)用 Thread.interrupt() 方法來停止正在執(zhí)行的任務(wù)的,因此某些無法響應(yīng)中斷的任務(wù)可能需要等到任務(wù)完成后才能停止線程

由于這兩個方法調(diào)用過后線程池都不會再接收新任務(wù)了,所以在回收空閑線程后,還需要檢查下線程是否都已經(jīng)回收完畢了,是的話則需要將線程池的生命周期狀態(tài)向 TIDYING 和 TERMINATED 遷移

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //在以下幾種情況不需要終止線程池:
        //1.還處于運(yùn)行狀態(tài)
        //2.已經(jīng)處于 TIDYING 或 TERMINATED 狀態(tài)
        //3.處于 SHUTDOWN 狀態(tài)且還有待處理的任務(wù)
        if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        //在達(dá)到 TIDYING 狀態(tài)前需要確保所有線程都被關(guān)閉了
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    //terminated() 方法執(zhí)行完畢后,線程池狀態(tài)就從 TIDYING 轉(zhuǎn)為 TERMINATED 了,此時線程池就走向終止了
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    //喚醒所有在等待線程池 TERMINATED 的線程
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

6、任務(wù)隊列的選擇

阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列為空時,獲取數(shù)據(jù)的線程會阻塞等待直到從隊列獲取到任務(wù)。當(dāng)隊列已滿時,存儲數(shù)據(jù)的線程會阻塞等待直到隊列空出位置可以存入數(shù)據(jù)。阻塞隊列常用于生產(chǎn)者和消費(fèi)者的場景,生產(chǎn)者是往隊列里添加數(shù)據(jù)的線程,消費(fèi)者是從隊列里取出數(shù)據(jù)的線程。阻塞隊列就是生產(chǎn)者存放數(shù)據(jù)的容器,而消費(fèi)者也只從容器里取數(shù)據(jù)

線程池實現(xiàn)解耦的關(guān)鍵就是有了 任務(wù)隊列/阻塞隊列 的存在。線程池中是以生產(chǎn)者消費(fèi)者模式+阻塞隊列來實現(xiàn)的,任務(wù)隊列負(fù)責(zé)緩存外部提交的任務(wù),線程負(fù)責(zé)從任務(wù)隊列取出任務(wù),這樣客戶端提交的任務(wù)就避免了和線程直接關(guān)聯(lián)

選擇不同的阻塞隊列可以實現(xiàn)不一樣的任務(wù)存取策略:

7、任務(wù)的拒絕策略

隨著客戶端不斷地提交任務(wù),當(dāng)前線程池大小也會不斷增加。在當(dāng)前線程池大小達(dá)到 corePoolSize 的時候,新提交的任務(wù)會被緩存到任務(wù)隊列之中,由線程后續(xù)不斷從隊列中取出任務(wù)并執(zhí)行。當(dāng)任務(wù)隊列滿了之后,線程池就會創(chuàng)建非核心線程。當(dāng)線程總數(shù)達(dá)到 maximumPoolSize 且所有線程都處于工作狀態(tài),同時任務(wù)隊列也滿了后,客戶端再次提交任務(wù)時就會被拒絕。而被拒絕的任務(wù)具體的處理策略則由 RejectedExecutionHandler 來進(jìn)行定義

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
    
}

當(dāng)客戶端提交的任務(wù)被拒絕時,線程池關(guān)聯(lián)的 RejectedExecutionHandler 對象的 rejectedExecution 方法就會被調(diào)用,相應(yīng)的拒絕策略可以由客戶端來指定

ThreadPoolExecutor 提供了以下幾種拒絕策略,默認(rèn)使用的是 AbortPolicy

實現(xiàn)類 策略
AbortPolicy 直接拋出異常,是 ThreadPoolExecutor 的默認(rèn)策略
DiscardPolicy 直接丟棄該任務(wù),不做任何響應(yīng)也不會拋出異常
DiscardOldestPolicy 如果線程池未被停止,則將工作隊列中最老的任務(wù)丟棄,然后嘗試接納該任務(wù)
CallerRunsPolicy 如果線程池未被停止,則直接在客戶端線程上執(zhí)行該任務(wù)

任務(wù)的拒絕策略只會在提交任務(wù)的時候被觸發(fā),即只在 execute(Runnable command) 方法中被觸發(fā)到。execute(Runnable command) 方法會判斷當(dāng)前狀態(tài)是否允許接受該任務(wù),如果不允許的話則會走拒絕任務(wù)的流程

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    if (isRunning(c) && workQueue.offer(command)) { //線程池還處于運(yùn)行狀態(tài)且成功添加任務(wù)到任務(wù)隊列
        //需要重新檢查下運(yùn)行狀態(tài)
        //因為等執(zhí)行到這里時,線程池可能被其它線程關(guān)閉了
        int recheck = ctl.get();

        //1、如果線程池已經(jīng)處于非運(yùn)行狀態(tài)了
        //1.1、如果移除 command 成功,則走拒絕策略
        //1.2、如果移除 command 失?。ㄒ驗?command 可能已經(jīng)被其它線程拿去執(zhí)行了),則走第 3 步
        //2、如果線程池還處于運(yùn)行狀態(tài),則走第 3 步
        //3、如果當(dāng)前線程數(shù)量為 0,則創(chuàng)建線程進(jìn)行處理
        //第 3 步的意義在于:corePoolSize 可以被設(shè)為 0,所以這里需要檢查下,在需要的時候創(chuàng)建一個非核心線程
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果線程池處于非運(yùn)行狀態(tài)了,或者是處于運(yùn)行狀態(tài)但隊列已滿了,此時就會走到這里
    //在這里嘗試創(chuàng)建一個非核心線程
    //如果線程創(chuàng)建失敗,說明要么是線程池當(dāng)前狀態(tài)大于等于 STOP,或者是任務(wù)隊列已滿且線程總數(shù)達(dá)到 maximumPoolSize 了
    //此時就走拒絕策略
    else if (!addWorker(command, false))
        reject(command);
}

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

8、監(jiān)控線程池的運(yùn)行狀態(tài)

ThreadPoolExecutor 提供了多個配置參數(shù)以便滿足多種不同的需求,這些配置參數(shù)包含:corePoolSize、maximumPoolSize、keepAliveTime、allowCoreThreadTimeOut 等。但很多時候我們一開始使用線程池時并不知道該如何配置參數(shù)才最為適應(yīng)當(dāng)前需求,那么就只能通過監(jiān)控線程池的運(yùn)行狀態(tài)來進(jìn)行考察,最終得到一份最合理的配置參數(shù)

可以通過 ThreadPoolExecutor 的以下幾個屬性來監(jiān)控線程池的運(yùn)行狀態(tài):

  1. taskCount:線程池已執(zhí)行結(jié)束(不管成功與否)的任務(wù)數(shù)加上任務(wù)隊列中目前包含的任務(wù)數(shù)
  2. completedTaskCount:線程池已執(zhí)行結(jié)束(不管成功與否)的任務(wù)數(shù),小于等于 taskCount
  3. largestPoolSize:線程池曾經(jīng)創(chuàng)建過的最大線程數(shù)量。如果該數(shù)值等于 maximumPoolSize 那就說明線程池曾經(jīng)滿過
  4. getPoolSize():獲取當(dāng)前線程總數(shù)
  5. getActiveCount():獲取當(dāng)前正在執(zhí)行任務(wù)的線程總數(shù)

此外,ThreadPoolExecutor 也預(yù)留了幾個鉤子方法可以由子類去實現(xiàn)。通過以下幾個方法,就可以實現(xiàn)每個任務(wù)開始執(zhí)行前和執(zhí)行后,以及線程池走向終止時插入一些自定義的監(jiān)控代碼,以此來實現(xiàn):計算任務(wù)的平均執(zhí)行時間、最小執(zhí)行時間和最大執(zhí)行時間等功能

protected void beforeExecute(Thread t, Runnable r) { }

protected void afterExecute(Runnable r, Throwable t) { }

protected void terminated() { }

四、Executors

Executors 是 JDK 提供的一個線程池創(chuàng)建工具類,封裝了很多個創(chuàng)建 ExecutorService 實例的方法,這里就來介紹下這幾個方法,這些線程池的差別主要都是由于選擇了不同的任務(wù)隊列導(dǎo)致的,讀者需要先認(rèn)識下以下幾種任務(wù)隊列

newFixedThreadPool方法創(chuàng)建的線程池,核心線程數(shù)和最大線程數(shù)都是 nThreads,所以線程池在任何時候最多也只會有 nThreads 個線程在同時運(yùn)行,且在停止線程池前所有線程都不會被回收。LinkedBlockingQueue 的默認(rèn)容量是 Integer.MAX_VALUE,近乎無限,在線程繁忙的情況下有可能導(dǎo)致等待處理的任務(wù)持續(xù)堆積,使得系統(tǒng)頻繁 GC,最終導(dǎo)致 OOM

此類線程池適合用于希望所有任務(wù)都能夠被執(zhí)行的情況

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

newSingleThreadExecutor方法創(chuàng)建的線程池,核心線程數(shù)和最大線程數(shù)都是 1,所以線程池在任何時候最多也只會有 1 個線程在同時運(yùn)行,且在停止線程池前所有線程都不會被回收。由于使用了 LinkedBlockingQueue,所以在極端情況下也是有發(fā)生 OOM 的可能

此類線程池適合用于執(zhí)行需要串行處理的任務(wù),或者是任務(wù)的提交間隔比任務(wù)的執(zhí)行時間長的情況

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

newCachedThreadPool方法創(chuàng)建的線程池,核心線程數(shù)是 0,最大線程數(shù)是 Integer.MAX_VALUE,所以允許同時運(yùn)行的線程數(shù)量近乎無限。再加上 SynchronousQueue 是一個不儲存元素的阻塞隊列,每當(dāng)有新任務(wù)到來時,如果當(dāng)前沒有空閑線程的話就會馬上啟動一個新線程來執(zhí)行任務(wù),這使得任務(wù)總是能夠很快被執(zhí)行,提升了響應(yīng)速度,但同時也存在由于要執(zhí)行的任務(wù)過多導(dǎo)致一直創(chuàng)建線程的可能性,這在任務(wù)耗時過長且任務(wù)量過多的情況下也可能導(dǎo)致 OOM

此類線程池適合用于對任務(wù)的處理速度要求比較高的情況

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

newScheduledThreadPool方法創(chuàng)建的線程池對應(yīng)的是 ScheduledThreadPoolExecutor,其繼承于 ThreadPoolExecutor 并實現(xiàn)了 ScheduledExecutorService 接口,在線程池的基礎(chǔ)上擴(kuò)展實現(xiàn)了執(zhí)行定時任務(wù)的能力。ScheduledThreadPoolExecutor 的核心線程數(shù)由入?yún)?corePoolSize 決定,最大線程數(shù)是 Integer.MAX_VALUE,keepAliveTime 是 0 秒,所以該線程池可能同時運(yùn)行近乎無限的線程,但一旦當(dāng)前沒有待執(zhí)行的任務(wù)的話,線程就會馬上被回收

此類線程池適合用于需要定時多次執(zhí)行特定任務(wù)的情況

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

newSingleThreadScheduledExecutor方法 newScheduledThreadPool 方法基本一樣,只是直接指定了核心線程數(shù)為 1

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}

五、線程池故障

1、線程池死鎖

多個線程會因為循環(huán)等待對方持有的排他性資源而導(dǎo)致死鎖,線程池也可能會因為多個任務(wù)間的相互依賴而導(dǎo)致線程池死鎖。例如,如果在線程池中執(zhí)行的任務(wù) A 在其執(zhí)行過程中又向同個線程池提交了任務(wù) B,且任務(wù) A 的執(zhí)行結(jié)束又依賴于任務(wù) B 的執(zhí)行結(jié)果,那么就可能出現(xiàn)這樣的一種極端情形:線程池中的所有正在執(zhí)行任務(wù)的線程都在等待其它任務(wù)的處理結(jié)果,而這些任務(wù)均在任務(wù)隊列中處于待執(zhí)行狀態(tài),且由于線程總數(shù)已經(jīng)達(dá)到線程池的最大線程數(shù)量限制,所以任務(wù)隊列中的任務(wù)就會一直無法被執(zhí)行,最終導(dǎo)致所有任務(wù)都無法完成,從而形成線程池死鎖

因此,提交給同一個線程池的任務(wù)必須是沒有互相依賴關(guān)系的。對于有依賴關(guān)系的任務(wù),應(yīng)該提交給不同的線程池,以此來避免死鎖的發(fā)生

2、線程泄漏

線程泄漏指由于某種原因?qū)е戮€程池中實際可用的線程變少的一種異常情況。如果線程泄漏持續(xù)存在,那么線程池中的線程會越來越少,最終使得線程池再也無法處理任務(wù)。導(dǎo)致線程泄露的原因可能有兩種:由于線程異常自動終止或者由于程序缺陷導(dǎo)致線程處于非有效運(yùn)行狀態(tài)。前者通常是由于 Thread.run() 方法中沒有捕獲到任務(wù)拋出的 Exception 或者 Error 導(dǎo)致的,使得相應(yīng)線程被提前終止而沒有相應(yīng)更新線程池當(dāng)前的線程數(shù)量,ThreadPoolExecutor 內(nèi)部已經(jīng)對這種情形進(jìn)行了預(yù)防。后者可能是由于客戶端提交的任務(wù)包含阻塞操作(Object.wait() 等操作),而該操作又沒有相應(yīng)的時間或者條件方面的限制,那么就有可能導(dǎo)致線程一直處于等待狀態(tài)而無法執(zhí)行其它任務(wù),這樣最終也是形成了線程泄漏

六、總結(jié)

線程池通過復(fù)用一定數(shù)量的線程來執(zhí)行不斷被提交的任務(wù),除了可以節(jié)約線程這種有限而昂貴的資源外,還包含以下好處:

  • 提高響應(yīng)速度。ThreadPoolExecutor 提供了預(yù)先創(chuàng)建一定數(shù)量的線程的功能,使得后續(xù)提交的任務(wù)可以立即被執(zhí)行而無須等待線程被創(chuàng)建,從而提高了系統(tǒng)的響應(yīng)速度
  • 抵消線程創(chuàng)建的開銷。一個線程可以先后用于執(zhí)行多個任務(wù),那創(chuàng)建線程帶來的成本(資源和時間)就可以看做是被平攤到其執(zhí)行的所有任務(wù)中。一個線程執(zhí)行的任務(wù)越多,那么創(chuàng)建該線程的“性價比”就越高
  • 封裝了任務(wù)的具體執(zhí)行過程。線程池封裝了每個線程在創(chuàng)建、管理、復(fù)用、回收等各個階段的邏輯,使得客戶端代碼只需要提交任務(wù)和獲取任務(wù)的執(zhí)行結(jié)果,而無須關(guān)心任務(wù)的具體執(zhí)行過程。即使后續(xù)想要將任務(wù)的執(zhí)行方式從并發(fā)改為串行,往往也只需要修改線程池內(nèi)部的處理邏輯即可,而無需修改客戶端代碼
  • 減少銷毀線程的開銷。JVM 在銷毀一個已經(jīng)停止的線程時也有著資源和時間方面的開銷,采用線程池可以避免頻繁地創(chuàng)建線程,從而減少了銷毀線程的次數(shù),減少了相應(yīng)開銷

七、參考資料

Java線程池實現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內(nèi)容