目前,多線程編程可以說是在大部分平臺和應(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)線程池
- 線程池中的線程最大數(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)整
- 線程池中的線程應(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)行等待
- 線程池中的線程可以一直存活著嗎?
程序運(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 的那一部分線程,就稱之為非核心線程
- 如何實現(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ù)用的目的
- 如何盡量實現(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)
- 當(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ò)展了一些功能:
- 擴(kuò)展執(zhí)行任務(wù)的能力。例如:獲取任務(wù)的執(zhí)行結(jié)果、取消任務(wù)等功能
- 提供了關(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é)果也影響著線程池的流程走向
- 當(dāng)前線程池大?。╟urrentPoolSize)。表示當(dāng)前實時狀態(tài)下線程池中線程的數(shù)量
- 最大線程池大小(maximumPoolSize)。表示線程池中允許存在的線程的數(shù)量上限
- 核心線程池大?。╟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,則可能是由于以下情況:
- 生命周期狀態(tài)大于等于 STOP
- 生命周期狀態(tài)等于 SHUTDOWN,但 firstTask 不為 null,或者任務(wù)隊列為空
- 當(dāng)前線程數(shù)已經(jīng)超出限制
- 符合創(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í)行階段
- Worker 在開始執(zhí)行任務(wù)前會執(zhí)行
Worker.lock()
,表明線程正在執(zhí)行任務(wù) - 如果 Worker 處于鎖定狀態(tài),則不應(yīng)該對其進(jìn)行中斷,避免任務(wù)執(zhí)行一半就被打斷
- 如果 Worker 處于非鎖定狀態(tài),說明其當(dāng)前是處于阻塞獲取任務(wù)的狀態(tài),此時才允許對其進(jìn)行中斷
- 線程池在執(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ī)制:
- 允許回收核心線程:allowCoreThreadTimeOut()
- 重置核心線程池大?。簊etCorePoolSize()
- 重置最大線程池大?。簊etMaximumPoolSize()
- 重置線程最大空閑時間:setKeepAliveTime()
- 關(guān)閉線程池:shutdown()
- 停止線程池: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)行線程的回收:
- 非核心線程的空閑時間超出了 keepAliveTime
- 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):
- taskCount:線程池已執(zhí)行結(jié)束(不管成功與否)的任務(wù)數(shù)加上任務(wù)隊列中目前包含的任務(wù)數(shù)
- completedTaskCount:線程池已執(zhí)行結(jié)束(不管成功與否)的任務(wù)數(shù),小于等于 taskCount
- largestPoolSize:線程池曾經(jīng)創(chuàng)建過的最大線程數(shù)量。如果該數(shù)值等于 maximumPoolSize 那就說明線程池曾經(jīng)滿過
- getPoolSize():獲取當(dāng)前線程總數(shù)
- 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)開銷