溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

死磕 java線程系列之線程池深入解析——生命周期

發(fā)布時間:2020-07-29 18:54:55 來源:網(wǎng)絡(luò) 閱讀:599 作者:艾弗森哇 欄目:編程語言

注:java源碼分析部分如無特殊說明均基于 java8 版本。

注:線程池源碼部分如無特殊說明均指ThreadPoolExecutor類。

簡介

上一章我們一起重溫了下線程的生命周期(六種狀態(tài)還記得不?),但是你知不知道其實(shí)線程池也是有生命周期的呢?!

問題

(1)線程池的狀態(tài)有哪些?

(2)各種狀態(tài)下對于任務(wù)隊(duì)列中的任務(wù)有何影響?

先上源碼

其實(shí),在我們講線程池體系結(jié)構(gòu)的時候,講了一些方法,比如shutDown()/shutDownNow(),它們都是與線程池的生命周期相關(guān)聯(lián)的。

我們先來看一下線程池ThreadPoolExecutor中定義的生命周期中的狀態(tài)及相關(guān)方法:

private?final?AtomicInteger?ctl?=?new?AtomicInteger(ctlOf(RUNNING,?0));private?static?final?int?COUNT_BITS?=?Integer.SIZE?-?3;?//?=29private?static?final?int?CAPACITY???=?(1?<<?COUNT_BITS)?-?1;?//?=000?11111...//?runState?is?stored?in?the?high-order?bitsprivate?static?final?int?RUNNING????=?-1?<<?COUNT_BITS;?//?111?00000...private?static?final?int?SHUTDOWN???=??0?<<?COUNT_BITS;?//?000?00000...private?static?final?int?STOP???????=??1?<<?COUNT_BITS;?//?001?00000...private?static?final?int?TIDYING????=??2?<<?COUNT_BITS;?//?010?00000...private?static?final?int?TERMINATED?=??3?<<?COUNT_BITS;?//?011?00000...//?線程池的狀態(tài)private?static?int?runStateOf(int?c)?????{?return?c?&?~CAPACITY;?}//?線程池中工作線程的數(shù)量private?static?int?workerCountOf(int?c)??{?return?c?&?CAPACITY;?}//?計算ctl的值,等于運(yùn)行狀態(tài)“加上”線程數(shù)量private?static?int?ctlOf(int?rs,?int?wc)?{?return?rs?|?wc;?}

從上面這段代碼,我們可以得出:

(1)線程池的狀態(tài)和工作線程的數(shù)量共同保存在控制變量ctl中,類似于AQS中的state變量,不過這里是直接使用的AtomicInteger,這里換成unsafe+volatile也是可以的;

(2)ctl的高三位保存運(yùn)行狀態(tài),低29位保存工作線程的數(shù)量,也就是說線程的數(shù)量最多只能有(2^29-1)個,也就是上面的CAPACITY;

(3)線程池的狀態(tài)一共有五種,分別是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED;

(4)RUNNING,表示可接受新任務(wù),且可執(zhí)行隊(duì)列中的任務(wù);

(5)SHUTDOWN,表示不接受新任務(wù),但可執(zhí)行隊(duì)列中的任務(wù);

(6)STOP,表示不接受新任務(wù),且不再執(zhí)行隊(duì)列中的任務(wù),且中斷正在執(zhí)行的任務(wù);

(7)TIDYING,所有任務(wù)已經(jīng)中止,且工作線程數(shù)量為0,最后變遷到這個狀態(tài)的線程將要執(zhí)行terminated()鉤子方法,只會有一個線程執(zhí)行這個方法;

(8)TERMINATED,中止?fàn)顟B(tài),已經(jīng)執(zhí)行完terminated()鉤子方法;

流程圖

下面我們再來看看這些狀態(tài)之間是怎么流轉(zhuǎn)的:

死磕 java線程系列之線程池深入解析——生命周期

(1)新建線程池時,它的初始狀態(tài)為RUNNING,這個在上面定義ctl的時候可以看到;

(2)RUNNING->SHUTDOWN,執(zhí)行shutdown()方法時;

(3)RUNNING->STOP,執(zhí)行shutdownNow()方法時;

(4)SHUTDOWN->STOP,執(zhí)行shutdownNow()方法時【本文由公從號“彤哥讀源碼”原創(chuàng)】;

(5)STOP->TIDYING,執(zhí)行了shutdown()或者shutdownNow()后,所有任務(wù)已中止,且工作線程數(shù)量為0時,此時會執(zhí)行terminated()方法;

(6)TIDYING->TERMINATED,執(zhí)行完terminated()方法后;

源碼分析

你以為貼個狀態(tài)的源碼,畫個圖就結(jié)束了嘛?那肯定不能啊,下面讓我們一起來看看源碼中是怎么控制的。

(1)RUNNING

RUNNING,比較簡單,創(chuàng)建線程池的時候就會初始化ctl,而ctl初始化為RUNNING狀態(tài),所以線程池的初始狀態(tài)就為RUNNING狀態(tài)。

//?初始狀態(tài)為RUNNINGprivate?final?AtomicInteger?ctl?=?new?AtomicInteger(ctlOf(RUNNING,?0));

(2)SHUTDOWN

執(zhí)行shutdown()方法時把狀態(tài)修改為SHUTDOWN,這里肯定會成功,因?yàn)閍dvanceRunState()方法中是個自旋,不成功不會退出。

public?void?shutdown()?{????final?ReentrantLock?mainLock?=?this.mainLock;
????mainLock.lock();????try?{
????????checkShutdownAccess();????????//?修改狀態(tài)為SHUTDOWN
????????advanceRunState(SHUTDOWN);????????//?標(biāo)記空閑線程為中斷狀態(tài)
????????interruptIdleWorkers();
????????onShutdown();
????}?finally?{
????????mainLock.unlock();
????}
????tryTerminate();
}private?void?advanceRunState(int?targetState)?{????for?(;;)?{????????int?c?=?ctl.get();????????//?如果狀態(tài)大于SHUTDOWN,或者修改為SHUTDOWN成功了,才會break跳出自旋
????????if?(runStateAtLeast(c,?targetState)?||
????????????ctl.compareAndSet(c,?ctlOf(targetState,?workerCountOf(c))))????????????break;
????}
}

(3)STOP

執(zhí)行shutdownNow()方法時,會把線程池狀態(tài)修改為STOP狀態(tài),同時標(biāo)記所有線程為中斷狀態(tài)。

public?List<runnable>?shutdownNow()?{
????List<runnable>?tasks;????final?ReentrantLock?mainLock?=?this.mainLock;
????mainLock.lock();????try?{
????????checkShutdownAccess();????????//?修改為STOP狀態(tài)
????????advanceRunState(STOP);????????//?標(biāo)記所有線程為中斷狀態(tài)
????????interruptWorkers();
????????tasks?=?drainQueue();
????}?finally?{????????//?【本文由公從號“彤哥讀源碼”原創(chuàng)】
????????mainLock.unlock();
????}
????tryTerminate();????return?tasks;
}

至于線程是否響應(yīng)中斷其實(shí)是在隊(duì)列的take()或poll()方法中響應(yīng)的,最后會到AQS中,它們檢測到線程中斷了會拋出一個InterruptedException異常,然后getTask()中捕獲這個異常,并且在下一次的自旋時退出當(dāng)前線程并減少工作線程的數(shù)量。

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

????for?(;;)?{????????int?c?=?ctl.get();????????int?rs?=?runStateOf(c);????????//?如果狀態(tài)為STOP了,這里會直接退出循環(huán),且減少工作線程數(shù)量
????????//?退出循環(huán)了也就相當(dāng)于這個線程的生命周期結(jié)束了
????????if?(rs?&gt;=?SHUTDOWN?&amp;&amp;?(rs?&gt;=?STOP?||?workQueue.isEmpty()))?{
????????????decrementWorkerCount();????????????return?null;
????????}????????int?wc?=?workerCountOf(c);????????//?Are?workers?subject?to?culling?
????????boolean?timed?=?allowCoreThreadTimeOut?||?wc?&gt;?corePoolSize;????????if?((wc?&gt;?maximumPoolSize?||?(timed?&amp;&amp;?timedOut))
????????????&amp;&amp;?(wc?&gt;?1?||?workQueue.isEmpty()))?{????????????if?(compareAndDecrementWorkerCount(c))????????????????return?null;????????????continue;
????????}????????try?{????????????//?真正響應(yīng)中斷是在poll()方法或者take()方法中
????????????Runnable?r?=?timed??
????????????????workQueue.poll(keepAliveTime,?TimeUnit.NANOSECONDS)?:
????????????????workQueue.take();????????????if?(r?!=?null)????????????????return?r;
????????????timedOut?=?true;
????????}?catch?(InterruptedException?retry)?{????????????//?這里捕獲中斷異常
????????????timedOut?=?false;
????????}
????}
}

這里有一個問題,就是已經(jīng)通過getTask()取出來且返回的任務(wù)怎么辦?

實(shí)際上它們會正常執(zhí)行完畢,有興趣的同學(xué)可以自己看看runWorker()這個方法,我們下一節(jié)會分析這個方法。焦作國醫(yī)胃腸醫(yī)院胃鏡檢查多少錢:http://jz.lieju.com/zhuankeyiyuan/37325002.htm

(4)TIDYING

當(dāng)執(zhí)行shutdown()或shutdownNow()之后,如果所有任務(wù)已中止,且工作線程數(shù)量為0,就會進(jìn)入這個狀態(tài)。

final?void?tryTerminate()?{????for?(;;)?{????????int?c?=?ctl.get();????????//?下面幾種情況不會執(zhí)行后續(xù)代碼
????????//?1.?運(yùn)行中
????????//?2.?狀態(tài)的值比TIDYING還大,也就是TERMINATED
????????//?3.?SHUTDOWN狀態(tài)且任務(wù)隊(duì)列不為空
????????if?(isRunning(c)?||
????????????runStateAtLeast(c,?TIDYING)?||
????????????(runStateOf(c)?==?SHUTDOWN?&amp;&amp;?!?workQueue.isEmpty()))????????????return;????????//?工作線程數(shù)量不為0,也不會執(zhí)行后續(xù)代碼
????????if?(workerCountOf(c)?!=?0)?{????????????//?嘗試中斷空閑的線程
????????????interruptIdleWorkers(ONLY_ONE);????????????return;
????????}????????final?ReentrantLock?mainLock?=?this.mainLock;
????????mainLock.lock();????????try?{????????????//?CAS修改狀態(tài)為TIDYING狀態(tài)
????????????if?(ctl.compareAndSet(c,?ctlOf(TIDYING,?0)))?{????????????????try?{????????????????????//?更新成功,執(zhí)行terminated鉤子方法
????????????????????terminated();
????????????????}?finally?{????????????????????//?強(qiáng)制更新狀態(tài)為TERMINATED,這里不需要CAS了
????????????????????ctl.set(ctlOf(TERMINATED,?0));
????????????????????termination.signalAll();
????????????????}????????????????return;
????????????}
????????}?finally?{
????????????mainLock.unlock();
????????}????????//?else?retry?on?failed?CAS
????}
}

實(shí)際更新狀態(tài)為TIDYING和TERMINATED狀態(tài)的代碼都在tryTerminate()方法中,實(shí)際上tryTerminated()方法在很多地方都有調(diào)用,比如shutdown()、shutdownNow()、線程退出時,所以說幾乎每個線程最后消亡的時候都會調(diào)用tryTerminate()方法,但最后只會有一個線程真正執(zhí)行到修改狀態(tài)為TIDYING的地方。http://m.qd8.com.cn/yiyao/xinxi21_3710011.html

修改狀態(tài)為TIDYING后執(zhí)行terminated()方法,最后修改狀態(tài)為TERMINATED,標(biāo)志著線程池真正消亡了。

(5)TERMINATED

見TIDYING中分析。

彩蛋

本章我們一起從狀態(tài)定義、流程圖、源碼分析等多個角度一起學(xué)習(xí)了線程池的生命周期,你掌握的怎么樣呢?

下一章我們將開始學(xué)習(xí)線程池執(zhí)行任務(wù)的主流程,對這一塊內(nèi)容感到恐懼的同學(xué)可以先看看彤哥之前寫的“手寫線程池”的兩篇文章,對接下來學(xué)習(xí)線程池的主要流程非常有好處。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI