一文讀懂線程池的實(shí)現(xiàn)原理

點(diǎn)擊上方老周聊架構(gòu)關(guān)注我
一、前言
上個月底群里的一個好朋友向老周提出啥時候分享 ThreadPoolExecutor 解析大全,我說后面會提上日程;然后前些天有讀者也反饋說在面試中有被問到線程池,問我啥時候出一篇線程池相關(guān)的文章。今天老周就來安排一波線程池,現(xiàn)在很多公司都喜歡問線程池相關(guān)的面試題,為什么面試官這么熱衷于問線程池相關(guān)的面試題呢?因為這是多線程的基礎(chǔ),ThreadPoolExecutor 的幾個重要參數(shù)你必須會知道設(shè)置以及什么場景選擇哪種 Executor 、線程池隊列的選擇以及相應(yīng)的拒絕策略。
下面老周收集了幾個朋友提供的大廠關(guān)于線程池的面試題:
線程池的使用場景
線程池各個參數(shù)的含義,你平時用的什么隊列以及拒絕策略?
程序中哪些地方用到了線程池,用線程池的好處有哪些?
如何自己實(shí)現(xiàn)一個線程池
JDK 提供了哪些線程池的默認(rèn)實(shí)現(xiàn)
阿里巴巴 Java 開發(fā)手冊為啥不允許默認(rèn)實(shí)現(xiàn)的線程池
線程池里的參數(shù)你是怎么得出來的,根據(jù)什么算出來的?
說說你自定義線程池里的工作流程
…
這里老周就不帶大家一個個對面試題進(jìn)行分析了,這里對只講核心原理再結(jié)合動態(tài)調(diào)整線程池參數(shù)的實(shí)踐來幫助你對線程池有個清晰的認(rèn)識,知道了原理再結(jié)合自己的實(shí)踐,那面試線程池也是得心應(yīng)手了。那你有可能問,老周啊,我平時也沒用到線程池啊,用的也都是定義類繼承 Thread 類或者定義類實(shí)現(xiàn) Runnable 接口來實(shí)現(xiàn)多線程的啊。額,如果你是面的 Java 中高級開發(fā),那你千萬不要這樣說,這會讓面試官一下覺得你不值中高級。如果你面的中高級還不知道線程池的話也沒關(guān)系,幸好你看到了老周這篇文章,還不算晚;如果你是已經(jīng)用過線程池相關(guān),那這篇文章也會讓你對線程池的原理更加清楚,在項目中應(yīng)用也會得心應(yīng)手。
二、線程池的概念
2.1 線程池是什么
線程池是一種線程使用模式。線程過多會帶來額外的開銷,其中包括創(chuàng)建銷毀線程的開銷、調(diào)度線程的開銷等等,同時也降低了計算機(jī)的整體性能。線程池維護(hù)多個線程,等待監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這種做法,一方面避免了處理任務(wù)時創(chuàng)建銷毀線程開銷的代價,另一方面避免了線程數(shù)量膨脹導(dǎo)致的過分調(diào)度問題,保證了對內(nèi)核的充分利用。
2.2 使用線程池的好處
降低資源消耗:通過池化技術(shù)重復(fù)利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷毀造成的損耗。
提高響應(yīng)速度:任務(wù)到達(dá)時,無需等待線程創(chuàng)建即可立即執(zhí)行。
提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導(dǎo)致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
提供更多更強(qiáng)大的功能:線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。比如延時定時線程池 ScheduledThreadPoolExecutor,就允許任務(wù)延期執(zhí)行或定期執(zhí)行。
2.3、ThreadPoolExecutor 的核心參數(shù)
網(wǎng)上說的天花亂墜的,也不如直接看 Doug Lea 大佬源碼的注釋來的更加貼切些。

corePoolSize:the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set
核心線程數(shù):線程池中保留的線程數(shù),即使它們是空閑的,除非設(shè)置 allowCoreThreadTimeOut。maximumPoolSize:the maximum number of threads to allow in the pool
最大線程數(shù):線程池中允許的最大線程數(shù)keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
線程空閑時間:如果經(jīng)過 keepAliveTime 時間后,超過核心線程數(shù)的線程還沒有接受到新的任務(wù),那就回收。unit:the time unit for the {@code keepAliveTime} argument
單位:keepAliveTime 的時間單位workQueue:the queue to use for holding tasks before they are executed. This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method.
存放待執(zhí)行任務(wù)的隊列:當(dāng)提交的任務(wù)數(shù)超過核心線程數(shù)后,再提交的任務(wù)就存放在這里。它僅僅用來存放被 execute 方法提交的 Runnable 任務(wù)。(這里不要再翻譯成工作隊列了好嗎)threadFactory:the factory to use when the executor creates a new thread
線程工廠:執(zhí)行程序創(chuàng)建新線程時使用的工廠。比如我們項目中自定義的線程工廠,排查問題的時候,根據(jù)線程工廠的名稱就知道這個線程來自哪里,很快的定位出問題,handler :the handler to use when execution is blocked because the thread bounds and queue capacities are reached
拒絕策略:當(dāng)隊列里面放滿了任務(wù)、最大線程數(shù)的線程都在工作時,這時繼續(xù)提交的任務(wù)線程池就處理不了,應(yīng)該執(zhí)行怎么樣的拒絕策略。
三、線程池的實(shí)現(xiàn)原理
本文描述線程池是 JDK 8 中提供的 ThreadPoolExecutor 類,那我們就從 ThreadPoolExecutor 類來看下它的 UML 依賴關(guān)系。
3.1 總體設(shè)計

藍(lán)色實(shí)線:繼承關(guān)系
綠色虛線:接口實(shí)現(xiàn)關(guān)系
綠色實(shí)現(xiàn):接口繼承關(guān)系
ThreadPoolExecutor 實(shí)現(xiàn)的頂層接口是 Executor,頂層接口只提供了void execute(Runnable command); 這么一個方法,Executor 提供的是一種思想:將任務(wù)提交和任務(wù)執(zhí)行進(jìn)行解耦。用戶無需關(guān)注如何創(chuàng)建線程,如何調(diào)度線程來執(zhí)行任務(wù),用戶只需提供 Runnable 對象,將任務(wù)的運(yùn)行邏輯提交到執(zhí)行器(Executor)中,由 Executor 框架完成線程的調(diào)配和任務(wù)的執(zhí)行部分。
ExecutorService 接口增加了一些能力:
擴(kuò)充執(zhí)行任務(wù)的能力,補(bǔ)充可以為一個或一批異步任務(wù)生成 Future 的方法;
提供了管控線程池的方法,比如停止線程池的運(yùn)行。
AbstractExecutorService 則是上層的抽象類,將執(zhí)行任務(wù)的流程串聯(lián)了起來,保證下層的實(shí)現(xiàn)只需關(guān)注一個執(zhí)行任務(wù)的方法即可。最下層的實(shí)現(xiàn)類 ThreadPoolExecutor 實(shí)現(xiàn)最復(fù)雜的運(yùn)行部分,ThreadPoolExecutor 將會一方面維護(hù)自身的生命周期,另一方面同時管理線程和任務(wù),使兩者良好的結(jié)合從而執(zhí)行并行任務(wù)。
我們來看下 ThreadPoolExecutor 的運(yùn)行流程:

線程池在內(nèi)部實(shí)際上構(gòu)建了一個生產(chǎn)者消費(fèi)者模型,將線程和任務(wù)兩者解耦,并不直接關(guān)聯(lián),從而良好的緩沖任務(wù),復(fù)用線程。線程池的運(yùn)行主要分成兩部分:任務(wù)管理、線程管理。
任務(wù)管理部分充當(dāng)生產(chǎn)者的角色,當(dāng)任務(wù)提交后,線程池會判斷該任務(wù)后續(xù)的流轉(zhuǎn):
直接申請線程執(zhí)行該任務(wù)
緩沖到隊列中等待線程執(zhí)行
拒絕該任務(wù)
線程管理部分充當(dāng)消費(fèi)者的角色,它們被統(tǒng)一維護(hù)在線程池內(nèi),根據(jù)任務(wù)請求進(jìn)行線程的分配,當(dāng)線程執(zhí)行完任務(wù)后則會繼續(xù)獲取新的任務(wù)去執(zhí)行,最終當(dāng)線程獲取不到任務(wù)的時候,線程就會被回收。
下面就從以下三個核心機(jī)制來詳細(xì)講解線程池運(yùn)行機(jī)制:
線程池如何維護(hù)自身狀態(tài)
線程池如何管理任務(wù)
線程池如何管理線程
3.2 線程池如何維護(hù)自身狀態(tài)
線程池運(yùn)行的狀態(tài),并不是用戶顯式設(shè)置的,而是伴隨著線程池的運(yùn)行,由內(nèi)部來維護(hù)。線程池內(nèi)部使用一個變量維護(hù)兩個值:運(yùn)行狀態(tài)(runState)和線程數(shù)量(workerCount)。

ctl 這個 AtomicInteger 類型,是對線程池的運(yùn)行狀態(tài)和線程池中有效線程的數(shù)量進(jìn)行控制的一個字段, 它同時包含兩部分的信息:線程池的運(yùn)行狀態(tài) (runState) 和線程池內(nèi)有效線程的數(shù)量 (workerCount),高 3 位保存 runState,低 29 位保存 workerCount,兩個變量之間互不干擾。用一個變量去存儲兩個值,可避免在做相關(guān)決策時,出現(xiàn)不一致的情況,不必為了維護(hù)兩者的一致,而占用鎖資源。通過閱讀線程池源代碼也可以發(fā)現(xiàn),經(jīng)常出現(xiàn)要同時判斷線程池運(yùn)行狀態(tài)和線程數(shù)量的情況。線程池也提供了若干方法去供用戶獲得線程池當(dāng)前的運(yùn)行狀態(tài)、線程個數(shù)。這里都使用的是位運(yùn)算的方式,相比于基本運(yùn)算,速度也會快很多。
關(guān)于內(nèi)部封裝的獲取生命周期狀態(tài)、獲取線程池線程數(shù)量的計算方法如下代碼:

哇,Doug Lea 大佬簡直了,設(shè)計的真好。老周等等我,這里怎么設(shè)計的就好了?CAPACITY 這里是多少呀?
不著急,老周這就帶你來分析分析為什么一個整型變量既可以保存運(yùn)行狀態(tài),又可以保存線程數(shù)量?
首先,我們知道 Java 中 1 個整型占 4 個字節(jié),也就是 32 位,所以 1 個整型有 32 位。
所以整型 1 用二進(jìn)制表示就是:0000 0000 0000 0000 0000 0000 0000 0001
整型 -1 用二進(jìn)制表示就是:1111 1111 1111 1111 1111 1111 1111 1111 (這個是補(bǔ)碼,這個忘了的話那得去復(fù)習(xí)下原碼、反碼、補(bǔ)碼等計算機(jī)基礎(chǔ)知識了。)
在 ThreadPoolExecutor,整型中 32 位的前 3 位用來表示線程池狀態(tài),后 29 位表示線程池中有效的線程數(shù)。

這里你有可能問了,老周啊,CAPACITY = (1 << 29) - 1 怎么就得到 0001 1111 1111 1111 1111 1111 1111 1111。
好吧,老周就帶你分析下 CAPACITY 怎么來的,下面的那些狀態(tài)大家也可以自己去分析下哈。
我們先來看 1 << 29,首先看 1 的二進(jìn)制代表 0000 0000 0000 0000 0000 0000 0000 0001。
然后 0000 0000 0000 0000 0000 0000 0000 0001 向左移 29 位,得到 0010 0000 0000 0000 0000 0000 0000 0000。
最后將 0010 0000 0000 0000 0000 0000 0000 0000 減 1 得到 0001 1111 1111 1111 1111 1111 1111 1111。
我們下面再來了解下 ThreadPoolExecutor 所定義的狀態(tài),這些狀態(tài)都和線程的執(zhí)行密切相關(guān):

RUNNING:能接受新提交的任務(wù),并且也能處理阻塞隊列中的任務(wù)。
SHUTDOWN:指調(diào)用了 shutdown() 方法,不再接受新提交的任務(wù),但卻可以繼續(xù)處理阻塞隊列中已保存的任務(wù)。
STOP:指調(diào)用了 shutdownNow() 方法,不再接受新提交的任務(wù),同時拋棄阻塞隊列里的所有任務(wù)并中斷所有正在執(zhí)行任務(wù)。
TIDYING:所有任務(wù)都執(zhí)行完畢,workerCount 有效線程數(shù)為 0。
TERMINATED:終止?fàn)顟B(tài),當(dāng)執(zhí)行 terminated() 后會更新為這個狀態(tài)。

3.3 線程池如何管理任務(wù)
3.3.1 任務(wù)調(diào)度
任務(wù)調(diào)度是線程池的主要入口,當(dāng)用戶提交了一個任務(wù),接下來這個任務(wù)將如何執(zhí)行都是由這個階段決定的。了解這部分就相當(dāng)于了解了線程池的核心運(yùn)行機(jī)制。
首先,所有任務(wù)的調(diào)度都是由 execute 方法完成的,比如我們業(yè)務(wù)代碼中 threadPool.execute(new Job());。
這部分完成的工作是:檢查現(xiàn)在線程池的運(yùn)行狀態(tài)、運(yùn)行線程數(shù)、運(yùn)行策略,決定接下來執(zhí)行的流程,是直接申請線程執(zhí)行,或是緩沖到隊列中執(zhí)行,亦或是直接拒絕該任務(wù)。其執(zhí)行過程如下:
首先檢測線程池運(yùn)行狀態(tài),如果不是 RUNNING,則直接拒絕,線程池要保證在 RUNNING 的狀態(tài)下執(zhí)行任務(wù)。
如果 workerCount < corePoolSize,則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)。
如果 workerCount >= corePoolSize,且線程池內(nèi)的阻塞隊列未滿,則將任務(wù)添加到該阻塞隊列中。
如果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內(nèi)的阻塞隊列已滿,則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)。
如果 workerCount >= maximumPoolSize,并且線程池內(nèi)的阻塞隊列已滿,則根據(jù)拒絕策略來處理該任務(wù),默認(rèn)的處理方式是直接拋異常。
執(zhí)行流程圖如下:

3.3.2 待執(zhí)行任務(wù)的隊列
待執(zhí)行任務(wù)的隊列是線程池能夠管理任務(wù)的核心部分。線程池的本質(zhì)是對任務(wù)和線程的管理,而做到這一點(diǎn)最關(guān)鍵的思想就是將任務(wù)和線程兩者解耦,不讓兩者直接關(guān)聯(lián),才可以做后續(xù)的分配工作。線程池中是以生產(chǎn)者消費(fèi)者模式,通過一個阻塞隊列來實(shí)現(xiàn)的。阻塞隊列緩存任務(wù),工作線程從阻塞隊列中獲取任務(wù)。
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。
這兩個附加的操作是:
在隊列為空時,獲取元素的線程會等待隊列變?yōu)榉强铡?/span>
當(dāng)隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產(chǎn)者和消費(fèi)者的場景,生產(chǎn)者是往隊列里添加元素的線程,消費(fèi)者是從隊列里拿元素的線程。阻塞隊列就是生產(chǎn)者存放元素的容器,而消費(fèi)者也只從容器里拿元素。
下圖中展示了 Thread1 往阻塞隊列中添加元素,而線程 Thread2 從阻塞隊列中移除元素:

使用不同的隊列可以實(shí)現(xiàn)不一樣的任務(wù)存取策略。我們下面來看下阻塞隊列的成員:

3.3.3 任務(wù)申請
從上文可知,任務(wù)的執(zhí)行有兩種可能:
一種是任務(wù)直接由新創(chuàng)建的線程執(zhí)行
另一種是線程從任務(wù)隊列中獲取任務(wù)然后執(zhí)行,執(zhí)行完任務(wù)的空閑線程會再次去從隊列中申請任務(wù)再去執(zhí)行。
第一種情況僅出現(xiàn)在線程初始創(chuàng)建的時候,第二種是線程獲取任務(wù)絕大多數(shù)的情況。
線程需要從待執(zhí)行任務(wù)的隊列中不斷地取任務(wù)執(zhí)行,幫助線程從阻塞隊列中獲取任務(wù),實(shí)現(xiàn)線程管理模塊和任務(wù)管理模塊之間的通信。
這部分策略由 getTask 方法實(shí)現(xiàn),我們來看下 getTask 方法的代碼。

getTask 方法在阻塞隊列中有待執(zhí)行的任務(wù)時會從隊列中彈出一個任務(wù)并返回,如果阻塞隊列為空,那么就會阻塞等待新的任務(wù)提交到隊列中直到超時(在一些配置下會一直等待而不超時),如果在超時之前獲取到了新的任務(wù),那么就會將這個任務(wù)作為返回值返回。所以一般 getTask 方法是不會返回 null 的,只會阻塞等待下一個任務(wù)并在之后將這個新任務(wù)作為返回值返回。
當(dāng) getTask 方法返回 null 時會導(dǎo)致當(dāng)前 Worker 退出,當(dāng)前線程被銷毀。在以下情況下 getTask 方法才會返回 null:
當(dāng)前線程池中的線程數(shù)超過了最大線程數(shù)。這是因為運(yùn)行時通過調(diào)用 setMaximumPoolSize 修改了最大線程數(shù)而導(dǎo)致的結(jié)果;
線程池處于 STOP 狀態(tài)。這種情況下所有線程都應(yīng)該被立即回收銷毀;
線程池處于 SHUTDOWN 狀態(tài),且阻塞隊列為空。這種情況下已經(jīng)不會有新的任務(wù)被提交到阻塞隊列中了,所以線程應(yīng)該被銷毀;
線程可以被超時回收的情況下等待新任務(wù)超時。線程被超時回收一般有以下兩種情況:
允許核心線程超時(線程池配置)的情況下線程等待任務(wù)超時
超出核心線程數(shù)部分的線程等待任務(wù)超時
3.3.4 任務(wù)拒絕
任務(wù)拒絕模塊是線程池的保護(hù)部分,線程池有一個最大的容量,當(dāng)線程池的任務(wù)緩存隊列已滿,并且線程池中的線程數(shù)目達(dá)到 maximumPoolSize 時,就需要拒絕掉該任務(wù),采取任務(wù)拒絕策略,保護(hù)線程池。
拒絕策略是一個接口,其設(shè)計如下:

用戶可以通過實(shí)現(xiàn)這個接口去定制拒絕策略,也可以選擇 JDK 提供的四種已有拒絕策略,其特點(diǎn)如下:

3.4 線程池如何管理線程
3.4.1 Worker線程
線程池為了掌握線程的狀態(tài)并維護(hù)線程的生命周期,設(shè)計了線程池內(nèi)的工作線程 Worker。我們來看一下它的代碼:

Worker 這個工作線程,實(shí)現(xiàn)了 Runnable 接口,并持有一個線程thread,一個初始化的任務(wù)firstTask。thread 是在調(diào)用構(gòu)造方法時通過 ThreadFactory 來創(chuàng)建的線程,可以用來執(zhí)行任務(wù);
firstTask 用它來保存?zhèn)魅氲牡谝粋€任務(wù),這個任務(wù)可以有也可以為 null。如果這個值是非空的,那么線程就會在啟動初期立即執(zhí)行這個任務(wù),也就對應(yīng)核心線程創(chuàng)建時的情況;如果這個值是空的,那么就需要創(chuàng)建一個線程去執(zhí)行任務(wù)列表(workQueue)中的任務(wù),也就是非核心線程的創(chuàng)建。
3.4.1.1 AQS 作用
Worker 繼承了 AbstractQueuedSynchronizer,主要目的有兩個:
將鎖的粒度細(xì)化到每個 Worker
如果多個 Worker 使用同一個鎖,那么一個 Worker Running 持有鎖的時候,其他 Worker 就無法執(zhí)行,這顯然是不合理的。直接使用 CAS 獲取,避免阻塞。
如果這個鎖使用阻塞獲取,那么在多 Worker 的情況下執(zhí)行 shutDown。如果這個 Worker 此時正在 Running 無法獲取到鎖,那么執(zhí)行 shutDown() 線程就會阻塞住了,顯然是不合理的。
3.4.1.2 Runnable 作用
Worker 還實(shí)現(xiàn)了 Runnable,它有兩個屬性 thead、firstTask。
firstTask 用它來保存?zhèn)魅氲牡谝粋€任務(wù),這個任務(wù)可以有也可以為 null。
如果這個值是非空的,那么線程就會在啟動初期立即執(zhí)行這個任務(wù),也就對應(yīng)核心線程創(chuàng)建時的情況。
如果這個值是 null,那么就需要創(chuàng)建一個線程去執(zhí)行任務(wù)列表(workQueue)中的任務(wù),也就是非核心線程的創(chuàng)建。
根據(jù)整體流程:
線程池調(diào)用 execute —> 創(chuàng)建 Worker(設(shè)置屬性thead、firstTask)—> worker.thread.start() —> 實(shí)際上調(diào)用的是 worker.run() —> 線程池的 runWorker(worker) —> worker.firstTask.run() (如果 firstTask 為 null 就從等待隊列中拉取一個)。
Worker 執(zhí)行任務(wù)的模型如下圖所示:

3.4.2 Worker 線程增加
增加線程是通過線程池中的 addWorker 方法,該方法的功能就是增加一個線程,該方法不考慮線程池是在哪個階段增加的該線程,這個分配線程的策略是在上個步驟完成的,該步驟僅僅完成增加線程,并使它運(yùn)行,最后返回是否成功這個結(jié)果。
addWorker 方法有兩個參數(shù):firstTask、core。
firstTask 參數(shù)用于指定新增的線程執(zhí)行的第一個任務(wù),該參數(shù)可以為空;
core 參數(shù)為 true 表示在新增線程時會判斷當(dāng)前活動線程數(shù)是否少于 corePoolSize,false 表示新增線程前需要判斷當(dāng)前活動線程數(shù)是否少于 maximumPoolSize。
我們來看一下 addWorker 的源碼:

源碼看著是不是挺費(fèi)勁的?沒關(guān)系,再看一張執(zhí)行流程圖加深下印象。

3.4.3 Worker 線程執(zhí)行任務(wù)
Worker 中的線程 start 的時候,調(diào)用 Worker 本身 run 方法,這個 run 方法調(diào)用外部類ThreadPoolExecutor 的 runWorker 方法,直接看 runWorker 方法的源碼:

執(zhí)行流程如下:
while 循環(huán)不斷地通過 getTask() 方法獲取任務(wù)
getTask() 方法從阻塞隊列中取任務(wù)
如果線程池正在停止,那么要保證當(dāng)前線程是中斷狀態(tài),否則要保證當(dāng)前線程不是中斷狀態(tài)。
執(zhí)行任務(wù)
如果 getTask 結(jié)果為 null 則跳出循環(huán),執(zhí)行 processWorkerExit() 方法,銷毀線程。

3.4.4 Worker 線程回收
線程池中線程的銷毀依賴 JVM 自動的回收,線程池做的工作是根據(jù)當(dāng)前線程池的狀態(tài)維護(hù)一定數(shù)量的線程引用,防止這部分線程被 JVM 回收,當(dāng)線程池決定哪些線程需要回收時,只需要將其引用消除即可。Worker 被創(chuàng)建出來后,就會不斷地進(jìn)行輪詢,然后獲取任務(wù)去執(zhí)行,核心線程可以無限等待獲取任務(wù),非核心線程要限時獲取任務(wù)。當(dāng) Worker 無法獲取到任務(wù),也就是獲取的任務(wù)為空時,循環(huán)會結(jié)束,Worker 會主動消除自身在線程池內(nèi)的引用。
線程回收的工作是在 processWorkerExit 方法完成的。

在回收 Worker 的時候線程池會嘗試結(jié)束自己的運(yùn)行,tryTerminate 方法:

3.4.4 Worker 線程關(guān)閉
說到線程關(guān)閉,我們就不得不來說說 shutdown 方法和 shutdownNow 方法。
3.4.4.1 shutdown

interruptIdleWorkers 方法,注意,這個方法打斷的是閑置 Worker,打斷閑置 Worker 之后,getTask 方法會返回 null,然后 Worker 會被回收。那什么是閑置 Worker 呢?
閑置 Worker 是這樣解釋的:Worker 運(yùn)行的時候會去阻塞隊列拿數(shù)據(jù)(getTask方法),拿的時候如果沒有設(shè)置超時時間,那么會一直阻塞等待阻塞隊列進(jìn)數(shù)據(jù),這樣的 Worker 就被稱為閑置 Worker。由于 Worker 也是一個 AQS,在 runWorker 方法里會有一對 lock 和 unlock 操作,這對 lock 操作是為了確保 Worker 不是一個閑置 Worker。
所以 Worker 被設(shè)計成一個 AQS 是為了根據(jù) Worker 的鎖來判斷是否是閑置線程,是否可以被強(qiáng)制中斷。
下面我們看下 interruptIdleWorkers 方法:

3.4.4.2 shutdownNow
shutdown 方法將線程池狀態(tài)改成 SHUTDOWN,線程池還能繼續(xù)處理阻塞隊列里的任務(wù),并且會回收一些閑置的 Worker。但是 shutdownNow 方法不一樣,它會把線程池狀態(tài)改成 STOP 狀態(tài),這樣不會處理阻塞隊列里的任務(wù),也不會處理新的任務(wù)。

shutdownNow 的中斷和 shutdown 方法不一樣,調(diào)用的是 interruptWorkers 方法:

3.4.4.3 Worker 線程關(guān)閉小結(jié)
shutdown 方法會更新狀態(tài)到 SHUTDOWN,不會影響阻塞隊列里任務(wù)的執(zhí)行,但是不會執(zhí)行新進(jìn)來的任務(wù)。同時也會回收閑置的 Worker,閑置 Worker 的定義上面已經(jīng)說過了。
shutdownNow 方法會更新狀態(tài)到 STOP,會影響阻塞隊列的任務(wù)執(zhí)行,也不會執(zhí)行新進(jìn)來的任務(wù)。同時會回收所有的 Worker。
這里老周就不寫總結(jié)了,每塊都分析的很清楚了。相信大家看完這篇文章,心里也有了自己想要的答案。
歡迎大家關(guān)注我的公眾號【老周聊架構(gòu)】,Java后端主流技術(shù)棧的原理、源碼分析、架構(gòu)以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。
喜歡的話,點(diǎn)贊、再看、分享三連。

點(diǎn)個在看你最好看
