adguard没有核心 core no_面试官:线程池如何按照core、max、queue的执行顺序去执行?...
前言
這是一個(gè)真實(shí)的面試題。
前幾天一個(gè)朋友在群里分享了他剛剛面試候選者時(shí)問(wèn)的問(wèn)題:"線程池如何按照core、max、queue的執(zhí)行循序去執(zhí)行?"。
我們都知道線程池中代碼執(zhí)行順序是:corePool->workQueue->maxPool,源碼我都看過(guò),你現(xiàn)在問(wèn)題讓我改源碼??
一時(shí)間群里炸開(kāi)了鍋,小伙伴們紛紛打聽(tīng)他所在的公司,然后拉黑避坑。(手動(dòng)狗頭,大家一起調(diào)侃?(?????)?)
關(guān)于線程池他一共問(wèn)了這么幾個(gè)問(wèn)題:
- 線程池如何按照core、max、queue的順序去執(zhí)行?
- 子線程拋出的異常,主線程能感知到么?
- 線程池發(fā)生了異常該怎樣處理?
全是一些有意思的問(wèn)題,我之前也寫(xiě)過(guò)一篇很詳細(xì)的圖文教程:【萬(wàn)字圖文-原創(chuàng)】 | 學(xué)會(huì)Java中的線程池,這一篇也許就夠了! ,不了解的小伙伴可以再回顧下~
但是針對(duì)這幾個(gè)問(wèn)題,可能大家一時(shí)間也有點(diǎn)懵。今天的文章我們以源碼為基礎(chǔ)來(lái)分析下該如何回答這三個(gè)問(wèn)題。(之前沒(méi)閱讀過(guò)源碼也沒(méi)關(guān)系,所有的分析都會(huì)貼出源碼及圖解)
線程池如何按照core、max、queue的順序執(zhí)行?
問(wèn)題思考
對(duì)于這個(gè)問(wèn)題,很多小伙伴肯定會(huì)疑惑:"別人源碼中寫(xiě)好的執(zhí)行流程你為啥要改?這面試官腦子有病吧……"
這里來(lái)思考一下現(xiàn)實(shí)工作場(chǎng)景中是否有這種需求?之前也看到過(guò)一份簡(jiǎn)歷也寫(xiě)到過(guò)這個(gè)問(wèn)題:
場(chǎng)景描述.png
一個(gè)線程池執(zhí)行的任務(wù)屬于IO密集型,CPU大多屬于閑置狀態(tài),系統(tǒng)資源未充分利用。如果一瞬間來(lái)了大量請(qǐng)求,如果線程池?cái)?shù)量大于coreSize時(shí),多余的請(qǐng)求都會(huì)放入到等待隊(duì)列中。等待著corePool中的線程執(zhí)行完成后再來(lái)執(zhí)行等待隊(duì)列中的任務(wù)。
試想一下,這種場(chǎng)景我們?cè)撊绾蝺?yōu)化?
我們可以修改線程池的執(zhí)行順序?yàn)?strong>corePool->maxPool->workQueue。 這樣就能夠充分利用CPU資源,提交的任務(wù)會(huì)被優(yōu)先執(zhí)行。當(dāng)線程池中線程數(shù)量大于maxSize時(shí)才會(huì)將任務(wù)放入等待隊(duì)列中。
你就說(shuō)巧不巧?面試官的這個(gè)問(wèn)題顯然是經(jīng)過(guò)認(rèn)真思考來(lái)提問(wèn)的,這是一個(gè)很有意思的問(wèn)題,下面就一起看看如何解決吧。
線程池運(yùn)行流程
我們都知道線程池執(zhí)行流程是先corePool再workQueue,最后才是maxPool的一個(gè)執(zhí)行流程。
執(zhí)行流程.png
線程池核心參數(shù)
在回顧下ThreadPoolExecutor.execute()源碼前我們先回顧下線程池中的幾個(gè)重要參數(shù):
線程池核心參數(shù).png
我們來(lái)看下這幾個(gè)參數(shù)的定義:corePoolSize: 線程池中核心線程數(shù)量maximumPoolSize: 線程池中最大線程數(shù)量keepAliveTime: 非核心的空閑線程等待新任務(wù)的時(shí)間 unit: 時(shí)間單位。配合allowCoreThreadTimeOut也會(huì)清理核心線程池中的線程。workQueue: 基于Blocking的任務(wù)隊(duì)列,最好選用有界隊(duì)列,指定隊(duì)列長(zhǎng)度threadFactory: 線程工廠,最好自定義線程工廠,可以自定義每個(gè)線程的名稱handler: 拒絕策略,默認(rèn)是AbortPolicy
ThreadPoolExecutor.execute()源碼分析
我們可以看下execute()如下:
execute執(zhí)行源碼.png
接著來(lái)分析下執(zhí)行過(guò)程:
好了,到了這里我們都已經(jīng)很清楚了,關(guān)鍵在于第二步和第三步如何交換順序執(zhí)行呢?
解決思路
仔細(xì)想一想,如果修改workQueue.offer()的實(shí)現(xiàn)不就可以達(dá)到目的了?我們先來(lái)畫(huà)圖來(lái)看一下:
問(wèn)題思路.png
現(xiàn)在的問(wèn)題就在于,如果當(dāng)前線程池中coreSize < workCount < maxSize時(shí),一定會(huì)先執(zhí)行offer()操作。
我們?nèi)绻薷膐ffer的實(shí)現(xiàn)是否可以完成執(zhí)行順序的更換呢?這里也是畫(huà)圖來(lái)展示一下:
解決方式.png
Dubbo中EagerThreadPool解決方案
湊巧Dubbo中也有類似的實(shí)現(xiàn),在Dubbo的EagerThreadPool自定義了一個(gè)BlockingQueue,在offer()方法中,如果當(dāng)前線程池?cái)?shù)量小于最大線程池時(shí),直接返回false,這里就達(dá)到了調(diào)節(jié)線程池執(zhí)行順序的目的。
dubbo中解決方案.png
源碼直達(dá):https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/TaskQueue.java
看到這里一切都真相大白了,解決思路以及方案都很簡(jiǎn)單,學(xué)會(huì)了沒(méi)有?
這個(gè)問(wèn)題背后還隱藏了一些場(chǎng)景的優(yōu)化、源碼的擴(kuò)展等等知識(shí),果然是一個(gè)值得思考的好問(wèn)題。
子線程拋出的異常,主線程能感知到么?
問(wèn)題思考
這個(gè)問(wèn)題其實(shí)也很容易回答,也僅僅是一個(gè)面試題而已,實(shí)際工作中子線程的異常不應(yīng)該由主線程來(lái)捕獲。
針對(duì)這個(gè)問(wèn)題,希望大家清楚的是: 我們要明確線程代碼的邊界,異步化過(guò)程中,子線程拋出的異常應(yīng)該由子線程自己去處理,而不是需要主線程感知來(lái)協(xié)助處理。
解決方案
解決方案很簡(jiǎn)單,在虛擬機(jī)中,當(dāng)一個(gè)線程如果沒(méi)有顯式處理異常而拋出時(shí)會(huì)將該異常事件報(bào)告給該線程對(duì)象的 java.lang.Thread.UncaughtExceptionHandler 進(jìn)行處理,如果線程沒(méi)有設(shè)置 UncaughtExceptionHandler,則默認(rèn)會(huì)把異常棧信息輸出到終端而使程序直接崩潰。
所以如果我們想在線程意外崩潰時(shí)做一些處理就可以通過(guò)實(shí)現(xiàn) UncaughtExceptionHandler 來(lái)滿足需求。
我們使用線程池設(shè)置ThreadFactory時(shí)可以指定UncaughtExceptionHandler,這樣就可以捕獲到子線程拋出的異常了。
代碼示例
具體代碼如下:
/**?*?測(cè)試子線程異常問(wèn)題?*?*?@author?wangmeng?*?@date?2020/6/13?18:08?*/public?class?ThreadPoolExceptionTest?{????public?static?void?main(String[]?args)?throws?InterruptedException?{????????MyHandler?myHandler?=?new?MyHandler();????????ExecutorService?execute?=?new?ThreadPoolExecutor(10,?10,????????????????0L,?TimeUnit.MILLISECONDS,?new?LinkedBlockingQueue(),?new?ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());????????TimeUnit.SECONDS.sleep(5);????????for?(int?i?=?0;?i?執(zhí)行結(jié)果:
執(zhí)行結(jié)果.png
UncaughtExceptionHandler 解析
我們來(lái)看下Thread中的內(nèi)部接口UncaughtExceptionHandler:
public?class?Thread?{????......????/**?????*?當(dāng)一個(gè)線程因未捕獲的異常而即將終止時(shí)虛擬機(jī)將使用?Thread.getUncaughtExceptionHandler()?????*?獲取已經(jīng)設(shè)置的?UncaughtExceptionHandler?實(shí)例,并通過(guò)調(diào)用其?uncaughtException(...)?方?????*?法而傳遞相關(guān)異常信息。?????*?如果一個(gè)線程沒(méi)有明確設(shè)置其?UncaughtExceptionHandler,則將其?ThreadGroup?對(duì)象作為其?????*?handler,如果?ThreadGroup?對(duì)象對(duì)異常沒(méi)有什么特殊的要求,則?ThreadGroup?會(huì)將調(diào)用轉(zhuǎn)發(fā)給?????*?默認(rèn)的未捕獲異常處理器(即?Thread?類中定義的靜態(tài)未捕獲異常處理器對(duì)象)。?????*?????*?@see?#setDefaultUncaughtExceptionHandler?????*?@see?#setUncaughtExceptionHandler?????*?@see?ThreadGroup#uncaughtException?????*/????@FunctionalInterface????public?interface?UncaughtExceptionHandler?{????????/**?????????*?未捕獲異常崩潰時(shí)回調(diào)此方法?????????*/????????void?uncaughtException(Thread?t,?Throwable?e);????}????/**?????*?靜態(tài)方法,用于設(shè)置一個(gè)默認(rèn)的全局異常處理器。?????*/????public?static?void?setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler?eh)?{?????????defaultUncaughtExceptionHandler?=?eh;?????}????/**?????*?針對(duì)某個(gè)?Thread?對(duì)象的方法,用于對(duì)特定的線程進(jìn)行未捕獲的異常處理。?????*/????public?void?setUncaughtExceptionHandler(UncaughtExceptionHandler?eh)?{????????checkAccess();????????uncaughtExceptionHandler?=?eh;????}????/**?????*?當(dāng)?Thread?崩潰時(shí)會(huì)調(diào)用該方法獲取當(dāng)前線程的?handler,獲取不到就會(huì)調(diào)用?group(handler?類型)。?????*?group?是?Thread?類的?ThreadGroup?類型屬性,在?Thread?構(gòu)造中實(shí)例化。?????*/????public?UncaughtExceptionHandler?getUncaughtExceptionHandler()?{????????return?uncaughtExceptionHandler?!=?null??????????????uncaughtExceptionHandler?:?group;????}????/**?????*?線程全局默認(rèn)?handler。?????*/????public?static?UncaughtExceptionHandler?getDefaultUncaughtExceptionHandler()?{????????return?defaultUncaughtExceptionHandler;????}????......}部分內(nèi)容參考自:https://mp.weixin.qq.com/s/ghnNQnpou6-NemhFjpl4Jg
線程池發(fā)生了異常該怎樣處理?
線程池中線程運(yùn)行過(guò)程中出現(xiàn)了異常該怎樣處理呢?線程池提交任務(wù)有兩種方式,分別是execute()和submit(),這里會(huì)依次說(shuō)明。
ThreadPoolExecutor.runWorker()實(shí)現(xiàn)
不管是使用execute()還是submit()提交任務(wù),最終都會(huì)執(zhí)行到ThreadPoolExecutor.runWorker(),我們來(lái)看下源碼(源碼基于JDK1.8):
runWorker().png
我們看到在執(zhí)行task.run()時(shí),出現(xiàn)異常會(huì)直接向上拋出,這里處理的最好的方式就是在我們業(yè)務(wù)代碼中使用try...catch()來(lái)捕獲異常。
FutureTask.run()實(shí)現(xiàn)
如果我們使用submit()來(lái)提交任務(wù),在ThreadPoolExecutor.runWorker()方法執(zhí)行時(shí)最終會(huì)調(diào)用到FutureTask.run()方法里面去,不清楚的小伙伴也可以看下我之前的文章:
線程池續(xù):你必須要知道的線程池submit()實(shí)現(xiàn)原理之FutureTask!
FutureTask.run().png
這里可以看到,如果業(yè)務(wù)代碼拋出異常后,會(huì)被catch捕獲到,然后調(diào)用setExeception()方法:
FutureTask.setException().png
可以看到其實(shí)類似于直接吞掉了,當(dāng)我們調(diào)用get()方法的時(shí)候異常信息會(huì)包裝到FutureTask內(nèi)部的變量outcome中,我們也會(huì)獲取到對(duì)應(yīng)的異常信息。
在ThreadPoolExecutor.runWorker()最后finally中有一個(gè)afterExecute()鉤子方法,如果我們重寫(xiě)了afterExecute()方法,就可以獲取到子線程拋出的具體異常信息Throwable了。
結(jié)論
對(duì)于線程池、包括線程的異常處理推薦以下方式:
3 直接重寫(xiě)afterExecute()方法,感知異常細(xì)節(jié)
總結(jié)
這篇文章到這里就結(jié)束了,不知道小伙伴們有沒(méi)有一些感悟或收獲?
通過(guò)這幾個(gè)面試問(wèn)題,我也深刻的感受到學(xué)習(xí)知識(shí)要多思考,看源碼的過(guò)程中要多設(shè)置一些場(chǎng)景,這樣才會(huì)收獲更多。
原創(chuàng)干貨分享.png
總結(jié)
以上是生活随笔為你收集整理的adguard没有核心 core no_面试官:线程池如何按照core、max、queue的执行顺序去执行?...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python中random库_pytho
- 下一篇: 写出表格的结构html,一个面试题,根据