聊聊Java進(jìn)階之并發(fā)基礎(chǔ)技術(shù)—線程池剖析

文章摘要:在業(yè)務(wù)系統(tǒng)中,線程池框架技術(shù)一直是用來解決多線程并發(fā)的一種有效方法。

在JDK中,J.U.C并發(fā)包下的ThreadPoolExecutor核心類是一種基于Executor接口的線程池框架,將任務(wù)提交和任務(wù)執(zhí)行解耦設(shè)計,其中ExecutorService和其各種實現(xiàn)類提供了非常方便的方式來提交任務(wù)并獲取任務(wù)執(zhí)行結(jié)果,并封裝了任務(wù)執(zhí)行的全部過程。本文將深入解讀并分析以ThreadPoolExecutor為核心的j.u.c包下Executor線程池框架的部分重要源代碼,一步步帶讀者搞清楚JDK中線程池框架背后的設(shè)計理念和運行機制。

一、線程池的概念和定義

自己在接觸線程池技術(shù)之前,“一直覺得在Java中有線程Thread對象,在業(yè)務(wù)需要的時候不斷地創(chuàng)建線程出來不一樣也能滿足需求么?”,如果大家跟我上面的這個想法一樣,不妨先來看下如下的內(nèi)容。

在服務(wù)器端的業(yè)務(wù)應(yīng)用開發(fā)中,Web服務(wù)器(諸如Tomcat、Jetty)需要接受并處理http請求,所以會為一個請求來分配一個線程來進(jìn)行處理。如果每次請求都新創(chuàng)建一個線程的話實現(xiàn)起來非常簡便,但是存在這樣的嚴(yán)重問題:

隨著業(yè)務(wù)量的增加,如果并發(fā)的請求數(shù)量非常多,但每個線程執(zhí)行的時間很短,這樣就會頻繁的創(chuàng)建和銷毀線程(包括涉及JVM的GC),如此一來會大大降低業(yè)務(wù)系統(tǒng)的效率。可能出現(xiàn)服務(wù)器在為每個請求創(chuàng)建新線程和銷毀線程上花費的時間和消耗的系統(tǒng)資源要比處理實際的用戶請求的時間和資源更多。

那么有沒有一種解決方案可以使線程在執(zhí)行完一個任務(wù)后,不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)呢?

這就是線程池的出現(xiàn)的原因了,其為線程生命周期的開銷和資源不足問題提供了解決方案。通過對多個任務(wù)重用線程,線程創(chuàng)建的開銷被分?jǐn)偟搅硕鄠€任務(wù)上。實際應(yīng)用中,上文所述Tomcat這樣的Web服務(wù)器也是利用線程池機制來接收并處理大量并發(fā)的http請求,可以通過其server.xml配置文件中的Connect節(jié)點的maxThreads(最大線程數(shù))/maxSpareThreads(最大空閑線程數(shù))/minSpareThreads(最小空閑線程數(shù))/acceptCount(最大等待隊列數(shù))/maxIdleTime(最大空閑時間)等參數(shù)進(jìn)行線程池調(diào)優(yōu)。

1.線程池的定義

線程池是一種多線程任務(wù)處理形式,處理過程中將任務(wù)添加到隊列,然后在創(chuàng)建線程后自動啟動這些任務(wù)。這里引用wiki上面的一個圖如下:

2.線程池使用的場景

(a)單個任務(wù)處理時間相對短

(b)需要處理的任務(wù)數(shù)量很大

3.線程池的主要作用

(a)降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

(b)提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

(c)提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

二、從一個簡單例子說起

在JDK 1.5后引入的Executor線程池框架的最大優(yōu)點是把任務(wù)的提交和執(zhí)行解耦。要執(zhí)行任務(wù)的人只需把Task描述清楚,然后提交即可。這個Task任務(wù)是怎么被執(zhí)行的,被誰執(zhí)行的,什么時候執(zhí)行的,提交的人就不用關(guān)心了。在最上面的Executor接口中定義了execute方法,該方法接收Runnable類型的任務(wù)命令,對用戶屏蔽底層線程的實現(xiàn)與調(diào)度細(xì)節(jié),這是一種典型命令設(shè)計模式的應(yīng)用。如下為一段非常簡單的線程池代碼例子:

? ? ? ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

? ? ? ? fixedThreadPool.execute(newRunnable() {

? ? ? ? @Override

? ? ? ? publicvoid run() {

? ? ? ? ? ? ? ? logger.info("thetask is running")

? ? ? ? }

});

在上面的例子中,生成線程池采用了工具類Executors的靜態(tài)方法,設(shè)置了線程池中核心線程數(shù)和最大線程數(shù)均為5,? ? 線程池中超過核心線程數(shù)目的空閑線程最大存活時間為0,同時使用LinkedBlockingQueue這樣子的無界阻塞任務(wù)隊列。除了newFixedThreadPool可以生成線程數(shù)固定大小的線程池,newCachedThreadPool可以生成一個可緩存且自動回收的線程池,newSingleThreadScheduledExecutor可以生成一個單個線程的線程池。newScheduledThreadPool還可以生成支持周期任務(wù)的線程池。

三、ThreadPoolExecutor線程池源碼剖析

JDK中的Executor線程池框架是一個根據(jù)一組執(zhí)行策略調(diào)用、調(diào)度、執(zhí)行和控制線程的異步任務(wù)框架,其目的是提供一種將“任務(wù)提交”與“任務(wù)運行”分離開來的機制。作為Java線程池框架中繼承Executor接口最為核心的類—ThreadPoolExecutor,有必要對其源代碼進(jìn)行深入分析。因此,本節(jié)以ThreadPoolExecutor的源代碼舉例,先對以Executor接口為核心的類結(jié)構(gòu)圖進(jìn)行一個較為全面的展示,然后回歸到源代碼中,對線程池中任務(wù)如何提交、如何執(zhí)行任務(wù)等方面分別進(jìn)行闡述。為了控制篇幅,突出主要邏輯,文章中引用的代碼片段去掉了非重點部分。

1.J.U.C線程池框架類圖


從上面的類結(jié)構(gòu)圖中可以看出,在JDK的J.U.C包下面主要包含了三個接口,分別是:

(a)Executor:一個運行新任務(wù)的簡單接口;

(b)ExecutorService:擴展了Executor接口,增加了一些用來管理線程池狀態(tài)和任務(wù)生命周期的方法以及支持Future返回值任務(wù)提交的方法;

(c)ScheduledExecutorService:擴展了ExecutorService,增加了定期和延遲任務(wù)執(zhí)行的方法;

這三個接口定義了JDK中Executor線程池框架的標(biāo)準(zhǔn)行為,這三個接口的具體代碼可以參考JDK的代碼。限于篇幅,這里就不對各個方法進(jìn)行一一詳細(xì)敘述了。類圖中AbstractExecutorService類是一個抽象類,提供了線程池框架的一些模板方法,具體實現(xiàn)由其子類,ThreadPoolExecutor和ScheduledThreadPoolExecutor分別實現(xiàn)。Executors是個工具類,里面提供了很多靜態(tài)方法,根據(jù)用戶的需求選擇返回不同的線程池實例。

對于類結(jié)構(gòu)圖中左邊半部分定義了ThreadPoolExecutor核心類的成員變量,包括創(chuàng)建線程的類工廠—ThreadFactory/DefaultThreadFactory,用以描述具體任務(wù)執(zhí)行線程的內(nèi)部類—Worker(其繼承AQS框架和Runnable接口)和提供線程池工作飽和策略的—RejectedExecutionHandler。

2.線程池的狀態(tài)

線程有五種狀態(tài):新建、就緒、運行、阻塞、死亡。對于線程池來說,同樣也具有五種狀態(tài):RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED。線程池狀態(tài)轉(zhuǎn)換圖如下:


? ? ? 在具體說說線程池的五種狀態(tài)之前有必要結(jié)合ThreadPoolExecutor核心類的代碼進(jìn)行一些分析,在該類的代碼中對于線程池的五種狀態(tài)定義如下:

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

? ? private static final int COUNT_BITS =Integer.SIZE - 3;

? ? private static final int CAPACITY? = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits

? ? private static final int RUNNING? ? = -1 << COUNT_BITS;

? ? private static final int SHUTDOWN? =? 0<< COUNT_BITS;

? ? private static final int STOP? ? ? = 1 << COUNT_BITS;

? ? private static final int TIDYING? ? =? 2<< COUNT_BITS;

? ? private static final int TERMINATED =? 3 << COUNT_BITS;

// Packing and unpacking ctl

? ? private static int runStateOf(int c)? ? { return c & ~CAPACITY; }

? ? ? private static int workerCountOf(int c)? { return c & CAPACITY; }

? ? private static int ctlOf(int rs, int wc) {return rs | wc; }

? ? ? 對于上面代碼中定義的常量ctl是對線程池的運行狀態(tài)和線程池中有效線程的數(shù)量進(jìn)行控制的一個32位字段,它包含兩部分的信息:其中高三位表示線程池的運行狀態(tài) (runState) ,低29位表示線程池內(nèi)有效線程的數(shù)量 (workerCount)。COUNT_BITS 就是29,CAPACITY就是1左移29位減1(29個1),這個常量表示workerCount的上限值,大約是5億。另外這里還定義了三個靜態(tài)方法分別為,runStateOf—獲取運行狀態(tài);workerCountOf—獲取活動線程數(shù);ctlOf—獲取運行狀態(tài)和活動線程數(shù)的值。

(a)RUNNING:處于RUNNING狀態(tài)的線程池能夠接受新任務(wù),以及對新添加的任務(wù)進(jìn)行處理。

(b)SHUTDOWN:處于SHUTDOWN狀態(tài)的線程池不可以接受新任務(wù),但是可以對已添加的任務(wù)進(jìn)行處理。

(c)STOP:處于STOP狀態(tài)的線程池不接收新任務(wù),不處理已添加的任務(wù),并且會中斷正在處理的任務(wù)。

(d)TIDYING:當(dāng)所有的任務(wù)已終止,ctl記錄的"任務(wù)數(shù)量"為0,線程池會變?yōu)門IDYING狀態(tài)。當(dāng)線程池變?yōu)門IDYING狀態(tài)時,會執(zhí)行鉤子函數(shù)terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變?yōu)門IDYING時,進(jìn)行相應(yīng)的處理;可以通過重載terminated()函數(shù)來實現(xiàn)。

(e)TERMINATED:線程池徹底終止的狀態(tài)。

3.創(chuàng)建Executor線程池方法

在上文第二節(jié)內(nèi)容中,已經(jīng)給出了一個創(chuàng)建簡單線程池的例子,其中調(diào)用了JDK的ThreadPoolExecutor核心類的構(gòu)造函數(shù)來創(chuàng)建的線程池實例。在這一節(jié)內(nèi)容中,通過分析ThreadPoolExecutor核心類的構(gòu)造函數(shù)以及參數(shù)來看下如何創(chuàng)建一個Executor線程池以及在創(chuàng)建時候需要關(guān)注哪些要素?

public ThreadPoolExecutor(intcorePoolSize,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? intmaximumPoolSize,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? longkeepAliveTime,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,

? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue workQueue,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ThreadFactorythreadFactory,

? ? ? ? ? ? ? ? ? ? ? ? ? ? RejectedExecutionHandler handler) {

? ? ? ? if (corePoolSize < 0 ||

? ? ? ? ? ? maximumPoolSize <= 0 ||

? ? ? ? ? ? maximumPoolSize < corePoolSize||

? ? ? ? ? ? keepAliveTime < 0)

? ? ? ? ? ? throw newIllegalArgumentException();

? ? ? ? if (workQueue == null || threadFactory== null || handler == null)

? ? ? ? ? ? throw new NullPointerException();

? ? ? ? this.corePoolSize = corePoolSize;

? ? ? ? this.maximumPoolSize = maximumPoolSize;

? ? ? ? this.workQueue = workQueue;

? ? ? ? this.keepAliveTime =unit.toNanos(keepAliveTime);

? ? ? ? this.threadFactory = threadFactory;

? ? ? ? this.handler = handler;

? ? }

(a)corePoolSize:該參數(shù)指的是線程池中核心線程的數(shù)量。當(dāng)提交一個任務(wù)時,線程池會新建一個線程來執(zhí)行任務(wù),直到當(dāng)前線程數(shù)等于corePoolSize。如果調(diào)用了線程池的prestartAllCoreThreads()方法,線程池會提前創(chuàng)建并啟動所有基本線程

(b)maximumPoolSize:從參數(shù)名稱上也應(yīng)該可以明白它的意思。該參數(shù)指的是線程池中允許的最大線程數(shù)。線程池的阻塞隊列滿了之后,如果還有任務(wù)提交,如果當(dāng)前的線程數(shù)小于maximumPoolSize,則會新建線程來執(zhí)行任務(wù)。這里有必要說明的是,如果使用的是無界隊列,該參數(shù)也就沒有什么效果了。

(c)keepAliveTime:該參數(shù)為線程空閑的時間。線程的創(chuàng)建和銷毀是需要代價的。線程執(zhí)行完任務(wù)后不會立即銷毀,而是繼續(xù)存活一段時間:keepAliveTime。默認(rèn)情況下,該參數(shù)只有在線程數(shù)大于corePoolSize時才會生效。

(d)unit:該參數(shù)用于表示keepAliveTime的單位。

(e)workQueue:該參數(shù)用來表示保存線程池中等待執(zhí)行任務(wù)的阻塞隊列,等待的任務(wù)需要實現(xiàn)Runnable接口。我們可以選擇這幾種:ArrayBlockingQueue:基于數(shù)組結(jié)構(gòu)的有界阻塞隊列,F(xiàn)IFO;LinkedBlockingQueue:基于鏈表結(jié)構(gòu)的無界阻塞隊列(如果設(shè)置初始化時的隊列大小),F(xiàn)IFO;SynchronousQueue:不存儲元素的阻塞隊列,每個插入操作都必須等待一個移出操作,反之亦然;PriorityBlockingQueue:具有優(yōu)先級別的阻塞隊列。這里的幾種阻塞隊列都為JDK中J.U.C并發(fā)包下較為經(jīng)典的阻塞隊列,其源碼值得閱讀和學(xué)習(xí),有興趣的朋友可以自己閱讀。

(f)threadFactory:該參數(shù)用于設(shè)置線程池中創(chuàng)建工作線程的工廠對象。該對象可以通過Executors.defaultThreadFactory()返回。

(g)handler:該參數(shù)為線程池的拒絕策略。所謂拒絕策略,是指將任務(wù)添加到線程池中時,線程池拒絕該任務(wù)所采取的相應(yīng)策略。當(dāng)向線程池中提交任務(wù)時,如果此時線程池中的線程已經(jīng)飽和了,而且阻塞隊列也已經(jīng)滿了,則線程池會選擇一種拒絕策略來處理該任務(wù)。線程池提供的拒絕策略主要有以下四種(位于上面J.U.C線程池的類結(jié)構(gòu)圖中左半部分):

AbortPolicy:直接拋出異常,默認(rèn)的線程池拒絕策略;

CallerRunsPolicy:用調(diào)用者所在的線程來完成待執(zhí)行的任務(wù);

DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務(wù),并執(zhí)行當(dāng)前任務(wù);

DiscardPolicy:直接丟棄任務(wù);

下面這個圖為線程池的邏輯結(jié)構(gòu)圖:

由上面的邏輯結(jié)構(gòu)圖可以初步知道線程池主要的執(zhí)行流程:

(a)當(dāng)有任務(wù)進(jìn)入時,線程池創(chuàng)建線程去執(zhí)行任務(wù),直到核心線程數(shù)滿為止;

(b)核心線程數(shù)量滿了之后,任務(wù)就會進(jìn)入一個緩沖的任務(wù)隊列中;

1、當(dāng)任務(wù)隊列為無界隊列時(諸如LinkedBlockingQueue隊列,通過構(gòu)造函數(shù)來配置為無界隊列),任務(wù)就會一直放入緩沖的任務(wù)隊列中,不會和最大線程數(shù)量進(jìn)行比較。

2、當(dāng)任務(wù)隊列為有界隊列時(諸如ArrayBlockingQueue隊列),任務(wù)先放入緩沖的任務(wù)隊列中,當(dāng)任務(wù)隊列滿了之后,才會將任務(wù)放入線程池,此時會與線程池中最大的線程數(shù)量進(jìn)行比較,如果超出了,則默認(rèn)會拋出異常進(jìn)行拒絕動作。否則,線程池才會執(zhí)行任務(wù)。當(dāng)任務(wù)執(zhí)行完,又會將緩沖隊列中的任務(wù)推入線程池中,然后重復(fù)此操作。

以下是線程池采用有界隊列來處理任務(wù)的主要流程如下圖所示:

4.線程池中任務(wù)提交與執(zhí)行

本節(jié)將介紹線程池中最為核心的任務(wù)提交代碼流程。Executor線程池可以根據(jù)業(yè)務(wù)需求的不同提供兩種方式提交任務(wù):Executor.execute()、ExecutorService.submit()。在實際應(yīng)用中,我們可以使用execute方法來提交沒有返回值的任務(wù)。因為沒有返回值,所以實際也沒有辦法知道任務(wù)是否被線程池中的線程執(zhí)行成功。通過以下的示例代碼可以提交一個沒有返回值的任務(wù):

threadpool.execute(newRunnable() {

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? public void run() {

? ? ? ? ? ? ? ? ? ? ? ? ? ? //TODO

? ? ? ? ? ? ? ? ? ? ? ? ? ? //logical code here

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? });

另外,我們也可以通過submit方法來提交帶有返回類型future的任務(wù),通過這個返回值可以判斷任務(wù)是否已經(jīng)成功執(zhí)行。通過future的get方法可以獲取任務(wù)執(zhí)行的返回值,這里需要說明的是get方法是阻塞的,直到任務(wù)返回為止,也可以通過get(long

timeout,TimeUnit unit方法)設(shè)置超時時間,避免一直阻塞等待。以下為submit方法提交任務(wù)的示例代碼:

Futurefuture = executor.submit();

? ? ? ? ? try{

? ? ? ? ? ? ? ? ? ? Object ret = future.get();

? ? ? ? ? }catch(InterruptedExceptione1){

? ? ? ? ? ? ? ? ? ? //處理中斷異常

? ? ? ? ? }catch(ExecutionExceptione2){

? ? ? ? ? ? ? ? ? ? //處理無法執(zhí)行任務(wù)異常

? ? ? ? ? }finally{

? ? ? ? ? ? ? ? ? ? //最后關(guān)閉線程池

? ? ? ? ? ? ? ? ? ? executor.shutdown();

}

限于篇幅,本文僅對ThreadPoolExecutor核心類的execute方法的實現(xiàn)進(jìn)行深入分析,而對submit方法則不做分析闡述,感興趣的朋友可以自行按照同樣的方法進(jìn)行分析(其實,通過看submit的源代碼可以發(fā)現(xiàn),它實際上還是調(diào)用的execute方法,只不過它利用了Future來獲取任務(wù)執(zhí)行結(jié)果)。execute方法的具體代碼如下:

publicvoid 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)) {

? ? ? ? ? ? int recheck = ctl.get();

? ? ? ? ? ? if (! isRunning(recheck) &&remove(command))

? ? ? ? ? ? ? ? reject(command);

? ? ? ? ? ? else if (workerCountOf(recheck) ==0)

? ? ? ? ? ? ? ? addWorker(null, false);

? ? ? ? }

? ? ? ? else if (!addWorker(command, false))

? ? ? ? ? ? reject(command);

? ? }

從以上的ThreadPoolExecutor類的execute方法的實現(xiàn)代碼中可以得出以下幾步主要的執(zhí)行流程:

Step1:如果線程池當(dāng)前線程數(shù)小于corePoolSize,則調(diào)用addWorker創(chuàng)建新線程執(zhí)行任務(wù),且方法返回,如果調(diào)用失敗執(zhí)行Step2;

Step2:如果線程池處于RUNNING狀態(tài),則嘗試加入待執(zhí)行任務(wù)的阻塞隊列;如果加入該隊列成功,則嘗試進(jìn)行Double Check;如果加入失敗,則執(zhí)行Step3;

Step3:如果線程池當(dāng)前為非RUNNING狀態(tài)或者加入阻塞隊列失敗,則嘗試創(chuàng)建新線程直到maxPoolSize;如果失敗,則調(diào)用reject()方法執(zhí)行相應(yīng)的飽和拒絕策略;

這里需要注意的是,在Step2中如果加入隊列成功,則會進(jìn)行一個雙重校驗的過程。其主要目的是判斷加入到阻塞隊列中的任務(wù)是否可以被執(zhí)行。如果線程池不是RUNNING狀態(tài),則調(diào)用remove()方法從阻塞隊列中刪除該任務(wù),然后調(diào)用reject()方法處理任務(wù)。否則需要確保還有線程執(zhí)行。

在上面的executor方法中多次調(diào)用了addWorker方法,可能已經(jīng)有同學(xué)在默默關(guān)注這個方法了。addWorker方法的主要工作是在線程池中創(chuàng)建一個新的線程并執(zhí)行,firstTask參數(shù)用于指定新增的線程執(zhí)行的第一個任務(wù),core參數(shù)為true表示在新增線程時會判斷當(dāng)前活動線程數(shù)是否少于corePoolSize,false表示新增線程前需要判斷當(dāng)前活動線程數(shù)是否少于maximumPoolSize。下面我們主要來看下這個addWork方法里面的具體實現(xiàn)代碼:

privateboolean addWorker(Runnable firstTask, boolean core) {

? ? ? ? retry:

? ? ? ? for (;;) {

? ? ? ? ? ? int c = ctl.get();

? ? ? ? ? ? ? ? ? ? ? //獲取當(dāng)前線程池的狀態(tài)

? ? ? ? ? ? int rs = runStateOf(c);

? ? ? ? ? ? if (rs >= SHUTDOWN &&

? ? ? ? ? ? ? ? ! (rs == SHUTDOWN &&

? ? ? ? ? ? ? ? ? firstTask == null &&

? ? ? ? ? ? ? ? ? ! workQueue.isEmpty()))

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? ? ? ? ? ? //內(nèi)層循環(huán),worker + 1

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //線程數(shù)量

? ? ? ? ? ? ? ? int wc = workerCountOf(c);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //如果當(dāng)前線程數(shù)大于線程最大上限CAPACITY? return false

? ? ? ? ? ? ? ? //若core ==

true,則與corePoolSize比較,否則與maximumPoolSize,大于returnfalse

? ? ? ? ? ? ? ? if (wc >= CAPACITY ||

? ? ? ? ? ? ? ? ? ? wc >= (core ?corePoolSize : maximumPoolSize))

? ? ? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //worker + 1,成功跳出retry循環(huán)

? ? ? ? ? ? ? ? if(compareAndIncrementWorkerCount(c))

? ? ? ? ? ? ? ? ? ? break retry;

? ? ? ? ? ? ? ? c = ctl.get();? // Re-read ctl

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //如果狀態(tài)不等于之前獲取的state,跳出內(nèi)層循環(huán),繼續(xù)去外層循環(huán)判斷

? ? ? ? ? ? ? ? if (runStateOf(c) != rs)

? ? ? ? ? ? ? ? ? ? continue retry;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? boolean workerStarted = false;

? ? ? ? boolean workerAdded = false;

? ? ? ? Worker w = null;

? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? //新建線程:Worker

? ? ? ? ? ? final ReentrantLock mainLock =this.mainLock;

? ? ? ? ? ? w = new Worker(firstTask);

? ? ? ? ? ? final Thread t = w.thread;

? ? ? ? ? ? if (t != null) {

? ? ? ? ? ? ? ? mainLock.lock();

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? int c = ctl.get();

? ? ? ? ? ? ? ? ? ? int rs = runStateOf(c);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // rs< SHUTDOWN ==>線程處于RUNNING狀態(tài)

// 或者線程處于SHUTDOWN狀態(tài),且firstTask

== null(可能是workQueue中仍有未執(zhí)行完成的任務(wù),創(chuàng)建沒有初始任務(wù)的worker線程執(zhí)行)

? ? ? ? ? ? ? ? ? ? if (rs < SHUTDOWN ||

? ? ? ? ? ? ? ? ? ? ? ? (rs == SHUTDOWN&& firstTask == null)) {

? ? ? ? ? ? ? ? ? ? ? ? //當(dāng)前線程已經(jīng)啟動,拋出異常

if(t.isAlive()) // precheck that t is startable

? ? ? ? ? ? ? ? ? ? ? ? ? ? throw newIllegalThreadStateException();

? ? ? ? ? ? ? ? ? ? ? ? workers.add(w);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //設(shè)置最大的池大小largestPoolSize,workerAdded設(shè)置為true

? ? ? ? ? ? ? ? ? ? ? ? int s = workers.size();

? ? ? ? ? ? ? ? ? ? ? ? if (s > largestPoolSize)

? ? ? ? ? ? ? ? ? ? ? ? ? ? largestPoolSize =s;

? ? ? ? ? ? ? ? ? ? ? ? workerAdded = true;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? ? ? mainLock.unlock();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //啟動線程

? ? ? ? ? ? ? ? if (workerAdded) {

? ? ? ? ? ? ? ? ? ? t.start();

? ? ? ? ? ? ? ? ? ? workerStarted = true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? } finally {

? ? ? ? ? ? if (! workerStarted)

? ? ? ? ? ? ? ? addWorkerFailed(w);

? ? ? ? }

? ? ? ? return workerStarted;

? ? }

如果對Executor線程池本身就有所了解的同學(xué)可能可以以上面的代碼加上注釋就可以明白addWorker方法中的具體含義了。但是這里仍然有必要再說下,在上面的addWorker方法的代碼中,主要完成了以下幾步流程:

Step1.首先,獲取線程池的狀態(tài)后先進(jìn)行條件的判斷,如果rs >= SHUTDOWN,則表示此時不再接收新任務(wù);其次,判斷以下三個條件只要有一個不滿足則addWorker方法返回false。這三個條件分別為:

(a)rs == SHUTDOWN,此時表示線程池處于關(guān)閉狀態(tài),不再接受新提交的任務(wù),但卻可以繼續(xù)處理阻塞隊列中已保存的任務(wù);

(b)firsTask為空;

(c)阻塞隊列不為空;

這里,首先考慮rs == SHUTDOWN的情況,在這種情況下不會接受新提交的任務(wù),所以在firstTask不為空的時候會返回false;其次,如果firstTask為空,并且workQueue也為空,則返回false,因為隊列中已經(jīng)沒有任務(wù)了,不需要再添加線程了。

Step2.這里先獲取線程數(shù),根據(jù)addWorker方法的第二個參數(shù)為true或者false進(jìn)行判斷。如果為true表示根據(jù)corePoolSize來比較,如果為false則根據(jù)maximumPoolSize來比較。然后,通過CAS進(jìn)行worker + 1。

Step3.獲取主鎖mailLock,隨后再次判斷線程池的狀態(tài)。如果線程池處于RUNNING狀態(tài)或者是處于SHUTDOWN狀態(tài)且 firstTask == null,則向線程池中添加線程,然后釋放主鎖mainLock并啟動線程,最后return true。如果中途失敗導(dǎo)致workerStarted= false,則調(diào)用addWorkerFailed()方法進(jìn)行處理。這里需要注意的是,t.start()這個語句,啟動時會調(diào)用Worker類中的run方法,Worker本身實現(xiàn)了Runnable接口,所以一個Worker類型的對象也是一個線程。

仔細(xì)的同學(xué)會發(fā)現(xiàn)上面addWorker方法的代碼中還有一個Worker對象,在線程池中每一個線程被封裝成一個Worker對象,線程池中維護(hù)的其實就是一組Worker對象,那么我們來看一下Worker的定義:

privatefinal class Worker

? ? ? ? extends AbstractQueuedSynchronizer

? ? ? ? implements Runnable

{

//省略代碼......

? ? /** Thread this worker is running in.? Null if factory fails. */

? ? final Thread thread;

? ? /** Initial task to run.? Possibly null. */

? ? Runnable firstTask;

? ? /**Per-thread task counter */

? ? volatile long completedTasks;


? ? /**

? ? * Creates with given first task and threadfrom ThreadFactory.

? ? * @param firstTask the first task (null ifnone)

? ? */

? ? Worker(Runnable firstTask) {

? ? ? ? setState(-1); // inhibit interruptsuntil runWorker

? ? ? ? this.firstTask = firstTask;

? ? ? ? this.thread =getThreadFactory().newThread(this);

? ? }

? ? /** Delegates main run loop to outerrunWorker? */

? ? public void run() {

? ? ? ? runWorker(this);

? ? }

//省略代碼......

? ? 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() { returnisHeldExclusively(); }


//省略代碼......

}

內(nèi)部Worker類繼承了AQS,并實現(xiàn)了Runnable接口,其中firstTask用它來保存?zhèn)魅氲娜蝿?wù);thread是在調(diào)用構(gòu)造方法時通過ThreadFactory來創(chuàng)建的線程,是用來處理任務(wù)的線程。在調(diào)用構(gòu)造方法時,需要把任務(wù)傳入,這里通過getThreadFactory().newThread(this);來新建一個線程,newThread方法傳入的參數(shù)是this,因為Worker本身繼承了Runnable接口,也就是一個線程,所以一個Worker對象在啟動的時候會調(diào)用Worker類中的run方法。同時,該類繼承了AQS框架,使用其來實現(xiàn)獨占鎖的功能。這里可能有同學(xué)會問為什么不使用ReentrantLock來實現(xiàn)呢?可以看到tryAcquire方法,它是不允許重入的,而ReentrantLock是允許重入的。這里,之所以設(shè)置為不可重入,是因為不希望任務(wù)在調(diào)用類似像setCorePoolSize這樣的線程池控制方法時重新獲取鎖,而去中斷正在運行的線程。

講到這里還沒有看到線程池任務(wù)運行的代碼,對Java線程Runnable接口比較熟悉的同學(xué)可能知道應(yīng)該是在上面的run方法來執(zhí)行具體的任務(wù)運行,那么下面我們再進(jìn)一步的看下runWorker方法里面究竟是干什么的?

finalvoid runWorker(Worker w) {

? ? Thread wt = Thread.currentThread();

? ? //獲取第一個任務(wù)

? ? Runnable task = w.firstTask;

? ? w.firstTask = null;

? ? //允許中斷

? ? w.unlock(); // allow interrupts

? ? //是否因為異常退出循環(huán)

? ? boolean completedAbruptly = true;

? ? try {

? ? ? ? //如果task為空,則通過getTask來獲取任務(wù)

? ? ? ? while (task != null || (task =getTask()) != null) {

? ? ? ? ? ? w.lock();

? ? ? ? ? ? // If pool is stopping, ensurethread is interrupted;

? ? ? ? ? ? // if not, ensure thread is notinterrupted.? This

? ? ? ? ? ? // requires a recheck in secondcase to deal with

? ? ? ? ? ? // shutdownNow race while clearinginterrupt

? ? ? ? ? ? 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 newError(x);

? ? ? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? ? ? afterExecute(task, thrown);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } finally {

? ? ? ? ? ? ? task = null;

? ? ? ? ? ? ? ? w.completedTasks++;

? ? ? ? ? ? ? ? w.unlock();

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? completedAbruptly = false;

? ? } finally {

? ? ? ? processWorkerExit(w,completedAbruptly);

? ? }

}


這里總結(jié)一下runWorker方法的執(zhí)行過程:

Step1.外層循環(huán)不斷地通過getTask()方法獲取任務(wù),其中g(shù)etTask()方法從阻塞隊列中取任務(wù);

Step2.如果線程池當(dāng)前正在停止,那么要保證當(dāng)前線程是中斷狀態(tài),否則要保證當(dāng)前線程不是中斷狀態(tài);

Step3.調(diào)用task.run()去執(zhí)行任務(wù),這里就是真正去執(zhí)行任務(wù)了;

Step4.如果task為null則跳出循環(huán),執(zhí)行processWorkerExit()方法;

上面代碼中的getTask()方法是用于從阻塞隊列中獲取任務(wù)的;processWorkerExit()方法則進(jìn)行收尾工作,統(tǒng)計完成的任務(wù)數(shù)目并從線程池中移除一個工作線程以銷毀工作線程。講到這里基本可以總結(jié)下整個工作線程的生命周期:從execute方法開始,Worker使用ThreadFactory創(chuàng)建新的工作線程,runWorker通過getTask獲取任務(wù),然后執(zhí)行任務(wù),如果getTask返回null,進(jìn)入processWorkerExit方法,整個線程結(jié)束。執(zhí)行流程圖如下:


限于篇幅所限,對于ThreadPoolExecutor核心類中的tryTerminate、shutdown、shutdownNow和interruptIdleWorkers等方法就不再在此進(jìn)行贅述了。感興趣的同學(xué)可以自己再閱讀這幾個類的源代碼。


四、如何合理應(yīng)用線程池

一般在我們自己的Spring-boot工程中都用如下的配置方式來注入線程池的Bean(Spring-MVC的工程是基于XML,配置大同小異)。不注意其中細(xì)節(jié)的同學(xué)覺得這里的幾個參數(shù)按照自己的感覺隨便設(shè)置即可,如果是自己做練習(xí)或者一些demo樣例的話,確實是無所謂,但是要應(yīng)用于生產(chǎn)實際環(huán)境的話,還是需要經(jīng)過一番分析和思考來設(shè)置線程池的參數(shù),才能滿足業(yè)務(wù)需求達(dá)到優(yōu)化系統(tǒng)性能的目標(biāo)。

@Bean(name= "taskAsyncPool")

public Executor taskAsyncPool() {

? ? ? ? ? ThreadPoolTaskExecutor executor = newThreadPoolTaskExecutor();

? ? ? ? ? executor.setCorePoolSize(corePoolSize);

? ? ? ? ? executor.setMaxPoolSize(maxPoolSize);

? ? ? ? ? executor.setQueueCapacity(queueCapacity);

? ? ? ? ? executor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);

? ? ? ? ? executor.setKeepAliveSeconds(keepAliveSeconds);

? ? ? ? ? executor.setThreadNamePrefix("ExecutorThread-");

? ? ? ? ? // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時候,如何處理新任務(wù)

? ? ? ? ? // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行

? ? ? ? ? executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());

? ? ? ? ? executor.initialize();

? ? ? ? ? return executor;

}

要合理地應(yīng)用線程池技術(shù),就需要首先對任務(wù)的特性有足夠的了解,可以從以下的幾個方面來分析:

(a)任務(wù)性質(zhì):提交的任務(wù)到底是CPU密集型,IO密集型還是兼有兩者的混合型;

(b)任務(wù)優(yōu)先級:任務(wù)是否具備低、中、高的優(yōu)先級;

(c)任務(wù)執(zhí)行時間:任務(wù)執(zhí)行時間需要較長、中、還是較短;

(d)任務(wù)依賴性:是否依賴其他系統(tǒng),比如數(shù)據(jù)庫連接;

首先,對于(a)我們可以根據(jù)任務(wù)性質(zhì)的不同用不同規(guī)模的線程池來進(jìn)行處理。CPU密集型的任務(wù)可以盡可能配置核心線程數(shù)較小的線程池,如配置Ncpu+1個線程的線程池。而對于IO密集型的任務(wù)由于任務(wù)長時間處于IO等待中,因此可以配置較多的線程,如2*Ncpu線程的線程池。如果對于混合型則通過適當(dāng)?shù)貙⑵洳鸱殖蒀PU密集型和IO密集型的分別處理。如果編程者不確定當(dāng)前機器的CPU核數(shù),JDK提供了Runtime.getRuntime().availableProcessors()方法進(jìn)行獲取。

對于(b)可以通過J.U.C并發(fā)包中的優(yōu)先級隊列—PriorityBlockingQueue來進(jìn)行任務(wù)處理。它可以讓高優(yōu)先級的任務(wù)先執(zhí)行,低優(yōu)先級任務(wù)延遲執(zhí)行。

對于(c)(d)可以根據(jù)任務(wù)不同執(zhí)行時間,分別建立不同規(guī)模類型的線程池來進(jìn)行任務(wù)處理。

另外,需要說明的是在實際應(yīng)用中,還是建議在線程池中設(shè)置有界隊列來初始化。因為有界隊列可以增加系統(tǒng)的穩(wěn)定性和預(yù)警設(shè)置,如果遇到線程池中線程數(shù)和任務(wù)隊列均滿的情況,可以直接執(zhí)行飽和拒絕策略并拋出異常告警。若是采用了無界隊列,則線程池中的任務(wù)隊列的任務(wù)數(shù)目會積壓得越來越多,最后撐爆服務(wù)器的內(nèi)存,造成整個系統(tǒng)不可用。

五、總結(jié)

本文從線程池的概念和定義出發(fā),簡單介紹了其使用場景和主要作用。然后從一個簡單的線程池樣例說起,闡述了Executor線程池框架的任務(wù)的提交和執(zhí)行解耦機制。通過給出J.U.C包下與線程池相關(guān)的類結(jié)構(gòu)圖,簡要介紹了以ThreadPoolExecutor為核心的相關(guān)接口和類,并結(jié)合代碼給出線程池幾種狀態(tài)定義以及轉(zhuǎn)換圖,詳細(xì)描述了創(chuàng)建線程池方法的ThreadPoolExecutor構(gòu)造函數(shù)。最后,以ThreadPoolExecutor類的execute方法為入口深入分析addWorker和runWorker核心方法的源代碼,梳理了線程池整體工作原理、生命周期和運行機制。在向線程池提交任務(wù)時,除本文敘述的execute方法外,還有一個submit方法,submit方法會返回一個Future對象用于獲取返回值,限于篇幅,有關(guān)Future和Callable將在其他篇幅中進(jìn)行介紹。限于筆者的才疏學(xué)淺,對JDK的Executor線程池可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。

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

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