自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

不同業(yè)務使用同一個線程池發(fā)生死鎖

開發(fā)
在我們進行代碼開發(fā)時,我也見過很多全局注冊一個自定義線程池,也許是業(yè)務量不高、也許是其他原因,反正全局可這一個線程池使勁造。

在我們進行代碼開發(fā)時,我也見過很多全局注冊一個自定義線程池(也有可能不是自定義的,直接使用更不推薦Executors 創(chuàng)建的線程池),也許是業(yè)務量不高、也許是其他原因,反正全局可這一個線程池使勁造。

一、看個代碼

業(yè)務邏輯代碼:

自定義線程池 BizThreadPool 代碼如下:

通過上方的代碼示例,如果你還沒有看出問題,那你可以停留幾秒思考一下。

自定義線程池創(chuàng)建,使用的這個隊列,嗯......,大家工作中一定不要這么用,此處只是為了做演示使用。

如果你已經(jīng)看出來了問題所在,也希望你能繼續(xù)看下去,驗證一下咱們是不是想的相同。

二、有啥問題

經(jīng)過短暫幾秒鐘的思考之后,決定還是運行一下 Demo 看看現(xiàn)象。

封裝一個 controller 直接啟動 Springboot 程序,Java 啟動。

啟動成功之后調用 GET http://localhost:8080/test/test,輸出結果如下。

按照我們的預期,日志中應該也要輸出子任務才對啊,怎么創(chuàng)建的子任務沒有輸出呢,看現(xiàn)象應該是沒有執(zhí)行。

那我們先執(zhí)行一下 jstack 命令看一下線程相關的信息,輸出信息中其中一段如下所示。

通過上面的堆棧信息可以看出,主線程在將父任務執(zhí)行完成之后,開啟了一個CountDownLatch并等待3個子任務執(zhí)行完成。

問題就在這,一直等待,一直等不到結果,所以就是我們剛開始看到的結果,只有父任務執(zhí)行了,子任務并沒有執(zhí)行。

一次調用沒有響應,多次調用之后,達到服務器資源瓶頸時系統(tǒng)就該發(fā)生崩潰了。

那么子任務為何沒有執(zhí)行到呢?

三、小試牛刀

首先我們從頭開始捋一下,先看下線程池的配置。

我們在創(chuàng)建自定義線程池時,核心線程與最大線程都設置的1,那我們直接修改最大線程數(shù)量,讓線程池有線程可以執(zhí)行子任務不就行了嗎?

對于生產(chǎn)中,核心線程與最大線程一般也不會設置為1,但是哪怕你設置為10、100、1000,極端情況下也會出現(xiàn)本文后面將要講述的問題。

說干就干,創(chuàng)建自定義線程池的代碼變?yōu)榱巳缦滦问健?/p>

非常自信的你重啟程序,然后調用接口,最終傻眼了,怎么沒變化?

如果你修改完最大線程數(shù)就去重啟程序的話,說明線程池的工作原理你已經(jīng)忘了!

好吧原諒你了,這次不準再忘了,下面跟我一起來看看這究竟是什么原因。

四、線程池工作流程

這里放一下線程池的工作流程。

面試官:線程池核心線程設置為0時任務執(zhí)行流程怎么樣的

知道了線程池的工作流程之后,在上述代碼中,哪怕增加了最大線程池的數(shù)量,最終子任務也并不會執(zhí)行到,我們可以打印一下當前線程池的狀態(tài)進行輔助觀察。(上述代碼的printThreadPoolStatus()方法會進行線程池當前狀態(tài)的打印)

調用一下GET http://localhost:8080/test/info方法查看線程池當前的狀態(tài)。

可以看到隊列中存在3個任務在排隊,等待線程池分配線程執(zhí)行任務。這也就是修改了最大線程池數(shù)量未生效的原因,因為還有一個無界隊列。

當然如果任務一直增加,隊列中任務數(shù)量越來越多,達到服務器的瓶頸,就會發(fā)生OOM了。(阿里開發(fā)規(guī)范中不推薦使用無界隊列的原因)

五、修改核心線程數(shù)量

那我們直接修改核心線程數(shù)量吧,核心線程超過任務數(shù)量?

回答:不行。

對于我們上面的例子來說,增加核心線程數(shù)量,擁有可以執(zhí)行子任務的線程,確實可以解決當下場景。

但是當并發(fā)量上來之后,或者說線程池的線程都被父線程所占用時,依舊會發(fā)現(xiàn)子任務無法獲得線程執(zhí)行。

此處我們修改核心線程為10執(zhí)行看一下輸出結果。

通過修改核心線程數(shù)量,解決了子任務在隊列中堆積的問題。

所以通過上述代碼,大家應該知道死鎖是怎么發(fā)生的了吧,這里我總結一下。

六、小結

當核心線程為1,最大線程為1,使用無界隊列。父任務在線程中等待子任務完成的通知,子任務在線程池的任務隊列中等待線程池調度線程資源。

當核心線程為1,最大線程為n,使用無界隊列。最大線程設置n與設置1沒有區(qū)別,除非使用的隊列不同,只要是使用的無界隊列,當資源耗盡之時,就是服務崩潰的時候。此時后面新的父任務到來時,也只會在任務隊列中繼續(xù)堆積。

當核心線程為n,最大線程為n,使用無界隊列。核心線程設置為n,意味著父線程大概率是可以執(zhí)行的,創(chuàng)建的子任務在任務隊列中排隊執(zhí)行。

當并發(fā)量上來,或者核心線程都被父任務所占據(jù)之后,線程池調用就變成了如下場景,所有的任務都被堆積在任務隊列當中:

核心線程全是父任務,后面創(chuàng)建的任務也都在任務隊列堆積,最終達到服務器瓶頸系統(tǒng)OOM。

七、最終解決方案

通過上述代碼示例,死鎖的根本原因在于,父任務會創(chuàng)建多個子任務,并等待子任務執(zhí)行結束,而父子任務都是使用的同一個線程池,當線程池中執(zhí)行線程都是父任務時,所有的子任務又都在任務隊列中等待執(zhí)行,所以這樣就會發(fā)生死鎖。

核心線程永遠不會釋放,從而造成任務隊列不斷堆積,直到OOM。

所以解決方案就是,隔離線程池。

不同的業(yè)務使用不同的線程池,使用一個新的線程池處理子任務,這樣就可以避免死鎖的發(fā)生了。

修改之后的代碼如下。

通過查看日志輸出可以發(fā)現(xiàn),線程池隔離之后,哪怕核心線程設置為1,也是可以正常執(zhí)行業(yè)務邏輯的,任務隊列中也沒有堆積任務。

八、總結

通過上面的 Demo 復現(xiàn)以及解決方案,在工作中優(yōu)化建議如下:

  • 禁止使用Executors創(chuàng)建自定義線程池。使用ThreadPoolExecutor創(chuàng)建線程池時,注意每個參數(shù)的含義,規(guī)避資源耗盡的風險。
  • 線程池使用有界隊列,避免使用無界隊列。
  • 對于父子任務的場景,可以使用線程池或者 MQ。使用有界隊列之后,制定合理的拒絕策略,拒絕策略可以考慮 MQ 做重試。
  • 不同業(yè)務使用不同的線程池,禁止父子任務使用相同的線程池。
責任編輯:趙寧寧 來源: 醉魚Java
相關推薦

2016-12-15 08:54:52

線程sessionopenSession

2020-09-29 12:15:13

生死鎖MySQL

2022-03-09 09:43:01

工具類線程項目

2009-06-09 12:38:12

NetBeanseclipse

2022-04-01 11:26:19

緩存數(shù)據(jù)庫讀寫策略

2020-10-27 13:24:35

線程池系統(tǒng)模型

2016-12-20 13:55:52

2019-08-20 10:24:39

HTTPSSSHLinux

2024-04-28 18:31:03

2022-07-26 00:00:02

TCPUDPMAC

2023-09-13 13:05:01

Java項目

2019-11-12 14:34:52

邊緣計算云計算企業(yè)

2024-03-18 08:21:06

TCPUDP協(xié)議

2024-03-05 10:07:22

TCPUDP協(xié)議

2019-10-10 14:50:17

快手英偉達

2023-11-17 16:06:14

2022-08-11 16:01:26

勒索軟件網(wǎng)絡攻擊

2025-01-07 08:20:00

2021-10-27 06:49:34

線程池Core函數(shù)

2021-08-16 20:48:34

嵌入式單片機信息
點贊
收藏

51CTO技術棧公眾號