日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

adguard没有核心 core no_面试官:线程池如何按照core、max、queue的执行顺序去执行?...

發(fā)布時(shí)間:2023/12/13 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 adguard没有核心 core no_面试官:线程池如何按照core、max、queue的执行顺序去执行?... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

這是一個(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ò)程:

  • 第一步:workerCountOf(c)時(shí)間計(jì)算當(dāng)前線程池中線程的個(gè)數(shù),當(dāng)線程個(gè)數(shù)小于核心線程數(shù)
  • 第二步:線程池線程數(shù)量大于核心線程數(shù),此時(shí)提交的任務(wù)會(huì)放入workQueue中,使用offer()進(jìn)行操作
  • 第三步:workQueue.offer()執(zhí)行失敗,新提交的任務(wù)會(huì)直接執(zhí)行,addWorker()會(huì)判斷如果當(dāng)前線程池?cái)?shù)量大于最大線程數(shù),則執(zhí)行拒絕策略
  • 好了,到了這里我們都已經(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ì)于線程池、包括線程的異常處理推薦以下方式:

  • 直接使用try/catch,這個(gè)也是最推薦的方式
  • 在我們構(gòu)造線程池的時(shí)候,重寫(xiě)uncaughtException()方法,上面示例代碼也有提到:
  • 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?

    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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。