adguard沒有核心 core no_面試官:線程池如何按照core、max、queue的執行順序去執行?...

前言

這是一個真實的面試題。

前幾天一個朋友在群里分享了他剛剛面試候選者時問的問題:"線程池如何按照core、max、queue的執行循序去執行?"

我們都知道線程池中代碼執行順序是:corePool->workQueue->maxPool,源碼我都看過,你現在問題讓我改源碼??

一時間群里炸開了鍋,小伙伴們紛紛打聽他所在的公司,然后拉黑避坑。(手動狗頭,大家一起調侃?(?????)?)

關于線程池他一共問了這么幾個問題:

  • 線程池如何按照core、max、queue的順序去執行?
  • 子線程拋出的異常,主線程能感知到么?
  • 線程池發生了異常該怎樣處理?

全是一些有意思的問題,我之前也寫過一篇很詳細的圖文教程:【萬字圖文-原創】 | 學會Java中的線程池,這一篇也許就夠了! ,不了解的小伙伴可以再回顧下~

但是針對這幾個問題,可能大家一時間也有點懵。今天的文章我們以源碼為基礎來分析下該如何回答這三個問題。(之前沒閱讀過源碼也沒關系,所有的分析都會貼出源碼及圖解)

線程池如何按照core、max、queue的順序執行?

問題思考

對于這個問題,很多小伙伴肯定會疑惑:"別人源碼中寫好的執行流程你為啥要改?這面試官腦子有病吧……"

這里來思考一下現實工作場景中是否有這種需求?之前也看到過一份簡歷也寫到過這個問題:

f98182fafa7d5564e8f9a4e5cb021d4b.png

場景描述.png

一個線程池執行的任務屬于IO密集型,CPU大多屬于閑置狀態,系統資源未充分利用。如果一瞬間來了大量請求,如果線程池數量大于coreSize時,多余的請求都會放入到等待隊列中。等待著corePool中的線程執行完成后再來執行等待隊列中的任務。

試想一下,這種場景我們該如何優化?

我們可以修改線程池的執行順序為corePool->maxPool->workQueue。 這樣就能夠充分利用CPU資源,提交的任務會被優先執行。當線程池中線程數量大于maxSize時才會將任務放入等待隊列中。

你就說巧不巧?面試官的這個問題顯然是經過認真思考來提問的,這是一個很有意思的問題,下面就一起看看如何解決吧。

線程池運行流程

我們都知道線程池執行流程是先corePool再workQueue,最后才是maxPool的一個執行流程。

51c345f985e47d4c4589dcffabf780e0.png

執行流程.png

線程池核心參數

在回顧下ThreadPoolExecutor.execute()源碼前我們先回顧下線程池中的幾個重要參數:

b42f66a89677899f670844f5d9eadecc.png

線程池核心參數.png

我們來看下這幾個參數的定義:corePoolSize: 線程池中核心線程數量maximumPoolSize: 線程池中最大線程數量keepAliveTime: 非核心的空閑線程等待新任務的時間 unit: 時間單位。配合allowCoreThreadTimeOut也會清理核心線程池中的線程。workQueue: 基于Blocking的任務隊列,最好選用有界隊列,指定隊列長度threadFactory: 線程工廠,最好自定義線程工廠,可以自定義每個線程的名稱handler: 拒絕策略,默認是AbortPolicy

ThreadPoolExecutor.execute()源碼分析

我們可以看下execute()如下:

253c27cbb516532e7c6c61041f5bee5d.png

execute執行源碼.png

接著來分析下執行過程:

  1. 第一步:workerCountOf(c)時間計算當前線程池中線程的個數,當線程個數小于核心線程數
  2. 第二步:線程池線程數量大于核心線程數,此時提交的任務會放入workQueue中,使用offer()進行操作
  3. 第三步:workQueue.offer()執行失敗,新提交的任務會直接執行,addWorker()會判斷如果當前線程池數量大于最大線程數,則執行拒絕策略

好了,到了這里我們都已經很清楚了,關鍵在于第二步和第三步如何交換順序執行呢?

解決思路

仔細想一想,如果修改workQueue.offer()的實現不就可以達到目的了?我們先來畫圖來看一下:

491a57b4b028f3206896188a4b7cd411.png

問題思路.png

現在的問題就在于,如果當前線程池中coreSize < workCount < maxSize時,一定會先執行offer()操作。

我們如果修改offer的實現是否可以完成執行順序的更換呢?這里也是畫圖來展示一下:

0488c75551bad432c7ee7ca3750cad00.png

解決方式.png

Dubbo中EagerThreadPool解決方案

湊巧Dubbo中也有類似的實現,在Dubbo的EagerThreadPool自定義了一個BlockingQueue,在offer()方法中,如果當前線程池數量小于最大線程池時,直接返回false,這里就達到了調節線程池執行順序的目的。

43fe79502bfe535612300ad2353feb27.png

dubbo中解決方案.png

源碼直達:https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/TaskQueue.java

看到這里一切都真相大白了,解決思路以及方案都很簡單,學會了沒有?

這個問題背后還隱藏了一些場景的優化、源碼的擴展等等知識,果然是一個值得思考的好問題。

子線程拋出的異常,主線程能感知到么?

問題思考

這個問題其實也很容易回答,也僅僅是一個面試題而已,實際工作中子線程的異常不應該由主線程來捕獲。

針對這個問題,希望大家清楚的是: 我們要明確線程代碼的邊界,異步化過程中,子線程拋出的異常應該由子線程自己去處理,而不是需要主線程感知來協助處理。

解決方案

解決方案很簡單,在虛擬機中,當一個線程如果沒有顯式處理異常而拋出時會將該異常事件報告給該線程對象的 java.lang.Thread.UncaughtExceptionHandler 進行處理,如果線程沒有設置 UncaughtExceptionHandler,則默認會把異常棧信息輸出到終端而使程序直接崩潰。

所以如果我們想在線程意外崩潰時做一些處理就可以通過實現 UncaughtExceptionHandler 來滿足需求。

我們使用線程池設置ThreadFactory時可以指定UncaughtExceptionHandler,這樣就可以捕獲到子線程拋出的異常了。

代碼示例

具體代碼如下:

/**?*?測試子線程異常問題?*?*?@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?

執行結果:

6d0577564e9a4d14f056d25013ce8508.png

執行結果.png

UncaughtExceptionHandler 解析

我們來看下Thread中的內部接口UncaughtExceptionHandler:

public?class?Thread?{????......????/**?????*?當一個線程因未捕獲的異常而即將終止時虛擬機將使用?Thread.getUncaughtExceptionHandler()?????*?獲取已經設置的?UncaughtExceptionHandler?實例,并通過調用其?uncaughtException(...)?方?????*?法而傳遞相關異常信息。?????*?如果一個線程沒有明確設置其?UncaughtExceptionHandler,則將其?ThreadGroup?對象作為其?????*?handler,如果?ThreadGroup?對象對異常沒有什么特殊的要求,則?ThreadGroup?會將調用轉發給?????*?默認的未捕獲異常處理器(即?Thread?類中定義的靜態未捕獲異常處理器對象)。?????*?????*?@see?#setDefaultUncaughtExceptionHandler?????*?@see?#setUncaughtExceptionHandler?????*?@see?ThreadGroup#uncaughtException?????*/????@FunctionalInterface????public?interface?UncaughtExceptionHandler?{????????/**?????????*?未捕獲異常崩潰時回調此方法?????????*/????????void?uncaughtException(Thread?t,?Throwable?e);????}????/**?????*?靜態方法,用于設置一個默認的全局異常處理器。?????*/????public?static?void?setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler?eh)?{?????????defaultUncaughtExceptionHandler?=?eh;?????}????/**?????*?針對某個?Thread?對象的方法,用于對特定的線程進行未捕獲的異常處理。?????*/????public?void?setUncaughtExceptionHandler(UncaughtExceptionHandler?eh)?{????????checkAccess();????????uncaughtExceptionHandler?=?eh;????}????/**?????*?當?Thread?崩潰時會調用該方法獲取當前線程的?handler,獲取不到就會調用?group(handler?類型)。?????*?group?是?Thread?類的?ThreadGroup?類型屬性,在?Thread?構造中實例化。?????*/????public?UncaughtExceptionHandler?getUncaughtExceptionHandler()?{????????return?uncaughtExceptionHandler?!=?null??????????????uncaughtExceptionHandler?:?group;????}????/**?????*?線程全局默認?handler。?????*/????public?static?UncaughtExceptionHandler?getDefaultUncaughtExceptionHandler()?{????????return?defaultUncaughtExceptionHandler;????}????......}

部分內容參考自:https://mp.weixin.qq.com/s/ghnNQnpou6-NemhFjpl4Jg

線程池發生了異常該怎樣處理?

線程池中線程運行過程中出現了異常該怎樣處理呢?線程池提交任務有兩種方式,分別是execute()和submit(),這里會依次說明。

ThreadPoolExecutor.runWorker()實現

不管是使用execute()還是submit()提交任務,最終都會執行到ThreadPoolExecutor.runWorker(),我們來看下源碼(源碼基于JDK1.8):

1fd4481f1f0459ef9c47c4ec3545cca5.png

runWorker().png

我們看到在執行task.run()時,出現異常會直接向上拋出,這里處理的最好的方式就是在我們業務代碼中使用try...catch()來捕獲異常。

FutureTask.run()實現

如果我們使用submit()來提交任務,在ThreadPoolExecutor.runWorker()方法執行時最終會調用到FutureTask.run()方法里面去,不清楚的小伙伴也可以看下我之前的文章:

線程池續:你必須要知道的線程池submit()實現原理之FutureTask!

cb2bcaf04ee820c4fef9a7c820e1722d.png

FutureTask.run().png

這里可以看到,如果業務代碼拋出異常后,會被catch捕獲到,然后調用setExeception()方法:

28829a17857d5582f1067282a3c134e5.png

FutureTask.setException().png

可以看到其實類似于直接吞掉了,當我們調用get()方法的時候異常信息會包裝到FutureTask內部的變量outcome中,我們也會獲取到對應的異常信息。

在ThreadPoolExecutor.runWorker()最后finally中有一個afterExecute()鉤子方法,如果我們重寫了afterExecute()方法,就可以獲取到子線程拋出的具體異常信息Throwable了。

結論

對于線程池、包括線程的異常處理推薦以下方式:

  1. 直接使用try/catch,這個也是最推薦的方式
  2. 在我們構造線程池的時候,重寫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 直接重寫afterExecute()方法,感知異常細節

總結

這篇文章到這里就結束了,不知道小伙伴們有沒有一些感悟或收獲?

通過這幾個面試問題,我也深刻的感受到學習知識要多思考,看源碼的過程中要多設置一些場景,這樣才會收獲更多。

950ffd00569313ee1d27ff3df29e4072.png

原創干貨分享.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446073.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446073.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446073.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux加密框架 crypto 算法管理 - 算法查找接口 crypto_larval_lookup

參考鏈接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客 crypto_larval_lookup函數介紹 crypto_larval_lookup函數的輸入參數包括待查找的算法名name、算法類型type和算法類型屏蔽位mask&#xff0c;查找命中時返回查找到的算法或注冊用算法幼…

python ssh 遠程登錄路由器執行命令_ssh批量登錄并執行命令(python實現)

局域網內有一百多臺電腦&#xff0c;全部都是linux操作系統&#xff0c;所有電腦配置相同&#xff0c;系統完全相同(包括用戶名和密碼)&#xff0c;ip地址是自動分配的。現在有個任務是在這些電腦上執行某些命令&#xff0c;者說進行某些操作&#xff0c;比如安裝某些軟件&…

linux加密框架 crypto 算法管理 - 算法查找接口 crypto_alg_lookup函數

參考鏈接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客 函數介紹 static struct crypto_alg *crypto_alg_lookup(const char *name, u32 type,u32 mask) {struct crypto_alg *alg;u32 test 0;if (!((type | mask) & CRYPTO_ALG_TESTED))…

linux加密框架 crypto 算法管理 - 動態和靜態算法管理

參考鏈接 Linux加密框架的算法管理&#xff08;三&#xff09;_家有一希的博客-CSDN博客 動態和靜態算法管理 靜態算法 加密框架中的算法分為靜態算法和動態算法兩種&#xff0c;其中靜態算法指的是以"算法名.ko"形式存在的靜態編譯的算法模塊&#xff0c;如aes.k…

3分鐘入門python_3分鐘帶你了解世界第一語言Python 入門上手也這么簡單!

一、Python入門1. Python爬蟲入門一之綜述Python爬蟲入門二之爬蟲基礎了解Python爬蟲入門三之Urllib庫的基本使用Python爬蟲入門四之Urllib庫的高級用法Python爬蟲入門五之URLError異常處理Python爬蟲入門六之Cookie的使用Python爬蟲入門七之正則表達式Python爬蟲入門八之Beaut…

linux加密框架 crypto 算法管理 - 算法檢測

參考鏈接 Linux加密框架的算法管理&#xff08;四&#xff09;_家有一希的博客-CSDN博客 函數介紹 如前所述&#xff0c;無論是靜態算法還是動態算法&#xff0c;算法注冊的最后一步都是進行算法正確性檢驗&#xff0c;一般流程是先調用__crypto_register_alg函數進行通用的算…

select選中的值_selenium下拉框處理(select)

前言 web自動化中&#xff0c;常見的場景還有一個下拉框的選擇&#xff0c;哪么在selenium中如何做下拉框的操作呢&#xff1f;selectselect在HTML中表示元素名&#xff0c;可創建單選或多選菜單。HTML中select長什么樣子&#xff1a;select在HTML中元素名&#xff0c;下面有選…

linux加密框架 crypto 算法管理 - 創建哈希算法實例

crypto_alloc_ahash函數 加密框架中的哈希算法可以是同步方式實現的也可以是異步方式實現的&#xff0c;但是算法應用不關注哈希算法的實現方式&#xff0c;關注的是哈希算法提供的算法接口。為實現統一管理&#xff0c;加密框架默認哈希算法的實現方式為異步方式&#xff0c;…

發票管理軟件_企業為什么需要ERP企業管理軟件?

對于一個制造企業來說&#xff0c;生產是企業最大的動力&#xff0c;而生產也需要進行優化管理&#xff0c;一個好的生產管理方式會帶給企業巨大的發展空間和利潤價值。對于一個制造企業來說&#xff0c;生產是企業最大的動力&#xff0c;而生產也需要進行優化管理&#xff0c;…

python 畫風場 scipy_Python數據分析及可視化實例之Scipy

強大到沒有朋友的科學計算庫&#xff0c;不知道怎么介紹ta&#xff01;大牛張若愚出了厚本的《Python 科學計算》第二版里面包羅萬象&#xff0c;就不做搬運工了&#xff0c;盡快開工pandas。來一彈在NLP自然語言處理中用到的稀疏矩陣處理&#xff1a;# coding: utf-8# # 稀疏矩…

linux加密框架 crypto 算法管理 - 應用角度講解加密框架的運行流程

參考鏈接 Linux加密框架的應用示例&#xff08;一&#xff09;_家有一希的博客-CSDN博客 本文大綱 本節將從應用角度說明加密框架的運行流程&#xff0c;包括加密框架如何管理算法、如何動態創建算法&#xff0c;應用模塊如何創建算法實例、如何通過算法實例調用算法接口等。…

java 累進計費率計算_設計費400萬,繳納所得稅100萬,如何籌劃

很多公司老板都會把利潤放在第一位&#xff0c;照理說這是沒錯的&#xff0c;公司要盈利才能繼續經營下去。我國有很多針對小微企業的政策&#xff0c;盈利不高的情況下&#xff0c;基本不會去考慮納稅問題&#xff0c;也沒有多少稅收壓力。但是對一些暴利的服務型行業、軟件設…

linux加密框架 crypto 算法管理 - 哈希算法應用實例

參考鏈接 Linux加密框架應用示例&#xff08;二&#xff09;_家有一希的博客-CSDN博客linux加密框架 crypto 算法管理 - 應用角度講解加密框架的運行流程_CHYabc123456hh的博客-CSDN博客 在應用模塊中創建并初始化哈希算法實例 假設某個SA配置使用的認證算法為"hmac(md5…

guido python正式發布年份_Python語言適合哪些領域的計算問題? (1.3分)_學小易找答案...

【單選題】關于Python中的復數,下列說法錯誤的是 (1.3分)【多選題】藥物作用的基本規律包括?【單選題】Python 中,以下哪個賦值操作符是錯誤的? (1.3分)【單選題】哪個選項是下面代碼的執行結果? s "abcd1234" print ( s . find ( "cd" )) (1.3分)【填…

Linux加密框架 crypto crypto_larval | crypto_larval_alloc | __crypto_register_alg 介紹

參考鏈接 Lniux加密框架中的主要數據結構&#xff08;五&#xff09;_家有一希的博客-CSDN博客crypto_larval struct crypto_larval {struct crypto_alg alg;struct crypto_alg *adult;struct completion completion;u32 mask; };結構體名叫 crypto_larval &#xff08;算法幼…

好玩的腳本代碼大全_Github | 推薦一個Python腳本集合項目

點擊上方"藍字"關注我們Python大數據分析記錄 分享 成長用python寫小腳本是一件好玩的事情&#xff0c;因為不是個大活兒&#xff0c;而且能解決眼邊前十分繁瑣的事情&#xff0c;這種輕松且便宜的代碼頗受人民群眾的歡迎~有點生活小妙招的意味大家較為熟知的腳本…

linux加密框架 crypto 算法管理 - 算法查找接口

參考鏈接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客linux加密框架 crypto 算法管理 - 算法查找接口 crypto_find_alg_CHYabc123456hh的博客-CSDN博客linux加密框架 crypto 算法管理 - 算法查找接口 crypto_alg_mod_lookup_CHYabc123456hh的…

python中以下關于列表描述錯誤的_10. 以下關于列表操作的描述,錯誤的是:_學小易找答案...

【單選題】Python 語句 a1211.21 print(type(a)) 的輸出結果為( )【單選題】Python語句序列"s1 [4, 5, 6]; s2 s1; s1[1]0; print(s2)"的運行結果是( )。【填空題】Python語句print(%d%%%d%(3/2, 3%2))的運行結果是( 1 )。【單選題】python語句print(type(123))的數…

寫論文注意事項

參考鏈接 給研究生修改了一篇論文后&#xff0c;該985博導幾近崩潰…… 重點分析 摘要與結論幾乎重合 這一條是我見過研究生論文中最常出現的事情&#xff0c;很多情況下&#xff0c;他們論文中摘要部分與結論部分重復率超過70%。對于摘要而言&#xff0c;首先要用一小句話引…

xml模糊查詢語句_2Mybatis學習筆記07:動態SQL語句(原創,轉載請注明來源)

開發環境&#xff1a;硬件環境&#xff1a;Windows10JDK 1.8&#xff1b; 軟件環境&#xff1a;JavaEclipseMybatismaven3.6tomcat8.0Postgresql 10.6&#xff1b; 用到的jar包&#xff1a; asm-3.3.1.jar cglib-2.2.2.jar commons-logging-1.1.1.jar javassist-3.17.1-GA.jar …