摘要:第二還是大家對線程池的理解不夠深刻,比如今天要探討的內(nèi)容。我認(rèn)為線程池它就是一個調(diào)度任務(wù)的工具。而在線程池這個場景中卻恰好就是要利用它只是一個普通方法調(diào)用。
背景
上周分享了一篇《一個線程罷工的詭異事件》,最近也在公司內(nèi)部分享了這個案例。
無獨(dú)有偶,在內(nèi)部分享的時候也有小伙伴問了之前分享時所提出的一類問題:
這其實(shí)是一類共性問題,我認(rèn)為主要還是兩個原因:
我自己確實(shí)也沒講清楚,之前畫的那張圖還需要再完善,有些誤導(dǎo)。
第二還是大家對線程池的理解不夠深刻,比如今天要探討的內(nèi)容。
線程池的工作原理首先還是來復(fù)習(xí)下線程池的基本原理。
我認(rèn)為線程池它就是一個調(diào)度任務(wù)的工具。
眾所周知在初始化線程池會給定線程池的大小,假設(shè)現(xiàn)在我們有 1000 個線程任務(wù)需要運(yùn)行,而線程池的大小為 10~20,在真正運(yùn)行任務(wù)的過程中他肯定不會創(chuàng)建這1000個線程同時運(yùn)行,而是充分利用線程池里這 10~20 個線程來調(diào)度這1000個任務(wù)。
而這里的 10~20 個線程最后會由線程池封裝為 ThreadPoolExecutor.Worker 對象,而這個 Worker 是實(shí)現(xiàn)了 Runnable 接口的,所以他自己本身就是一個線程。
深入分析這里我們來做一個模擬,創(chuàng)建了一個核心線程、最大線程數(shù)、阻塞隊列都為2的線程池。
這里假設(shè)線程池已經(jīng)完成了預(yù)熱,也就是線程池內(nèi)部已經(jīng)創(chuàng)建好了兩個線程 Worker。
當(dāng)我們往一個線程池丟一個任務(wù)會發(fā)生什么事呢?
第一步是生產(chǎn)者,也就是任務(wù)提供者他執(zhí)行了一個 execute() 方法,本質(zhì)上就是往這個內(nèi)部隊列里放了一個任務(wù)。
之前已經(jīng)創(chuàng)建好了的 Worker 線程會執(zhí)行一個 while 循環(huán) ---> 不停的從這個內(nèi)部隊列里獲取任務(wù)。(這一步是競爭的關(guān)系,都會搶著從隊列里獲取任務(wù),由這個隊列內(nèi)部實(shí)現(xiàn)了線程安全。)
獲取得到一個任務(wù)后,其實(shí)也就是拿到了一個 Runnable 對象(也就是 execute(Runnable task) 這里所提交的任務(wù)),接著執(zhí)行這個 Runnable 的 run() 方法,而不是 start(),這點(diǎn)需要注意后文分析原因。
結(jié)合源碼來看:
從圖中其實(shí)就對應(yīng)了剛才提到的二三兩步:
while 循環(huán),從 getTask() 方法中一直不停的獲取任務(wù)。
拿到任務(wù)后,執(zhí)行它的 run() 方法。
這樣一個線程就調(diào)度完畢,然后再次進(jìn)入循環(huán)從隊列里取任務(wù)并不斷的進(jìn)行調(diào)度。
再次解釋之前的問題接下來回顧一下我們上一篇文章所提到的,導(dǎo)致一個線程沒有運(yùn)行的根本原因是:
在單個線程的線程池中一但拋出了未被捕獲的異常時,線程池會回收當(dāng)前的線程并創(chuàng)建一個新的 Worker;
它也會一直不斷的從隊列里獲取任務(wù)來執(zhí)行,但由于這是一個消費(fèi)線程,根本沒有生產(chǎn)者往里邊丟任務(wù),所以它會一直 waiting 在從隊列里獲取任務(wù)處,所以也就造成了線上的隊列沒有消費(fèi),業(yè)務(wù)線程池沒有執(zhí)行的問題。
結(jié)合之前的那張圖來看:
這里大家問的最多的一個點(diǎn)是,為什么會沒有是根本沒有生產(chǎn)者往里邊丟任務(wù),圖中不是明明畫的有一個 product 嘛?
這里確實(shí)是有些不太清楚,再次強(qiáng)調(diào)一次:
圖中的 product 是往內(nèi)部隊列里寫消息的生產(chǎn)者,并不是往這個 Consumer 所在的線程池中寫任務(wù)的生產(chǎn)者。
因?yàn)榧幢?Consumer 是一個單線程的線程池,它依然具有一個常規(guī)線程池所具備的所有條件:
Worker 調(diào)度線程,也就是線程池運(yùn)行的線程;雖然只有一個。
內(nèi)部的阻塞隊列;雖然長度只有1。
再次結(jié)合圖來看:
所以之前提到的【沒有生產(chǎn)者往里邊丟任務(wù)】是指右圖放大后的那一塊,也就是內(nèi)部隊列并沒有其他線程往里邊丟任務(wù)執(zhí)行 execute() 方法。
而一旦發(fā)生未捕獲的異常后,Worker1 被回收,順帶的它所調(diào)度的線程 task1(這個task1 也就是在執(zhí)行一個 while 循環(huán)消費(fèi)左圖中的那個隊列) 也會被回收掉。
新創(chuàng)建的 Worker2 會取代 Worker1 繼續(xù)執(zhí)行 while 循環(huán)從內(nèi)部隊列里獲取任務(wù),但此時這個隊列就一直會是空的,所以也就是處于 Waiting 狀態(tài)。
我覺得這波解釋應(yīng)該還是講清楚了,歡迎還沒搞明白的朋友留言討論。為什是 run() 而不是 start()
問題搞清楚后來想想為什么線程池在調(diào)度的時候執(zhí)行的是 Runnable 的 run() 方法,而不是 start() 方法呢?
我相信大部分沒有看過源碼的同學(xué)心中第一個印象就應(yīng)該是執(zhí)行的 start() 方法;
因?yàn)椴还苁菍W(xué)校老師,還是網(wǎng)上大牛講的都是只有執(zhí)行了 start() 方法后操作系統(tǒng)才會給我們創(chuàng)建一個獨(dú)立的線程來運(yùn)行,而 run() 方法只是一個普通的方法調(diào)用。
而在線程池這個場景中卻恰好就是要利用它只是一個普通方法調(diào)用。
回到我在文初中所提到的:我認(rèn)為線程池它就是一個調(diào)度任務(wù)的工具。
假設(shè)這里是調(diào)用的 Runnable 的 start 方法,那會發(fā)生什么事情。
如果我們往一個核心、最大線程數(shù)為 2 的線程池里丟了 1000 個任務(wù),那么它會額外的創(chuàng)建 1000 個線程,同時每個任務(wù)都是異步執(zhí)行的,一下子就執(zhí)行完畢了。
從而沒法做到由這兩個 Worker 線程來調(diào)度這 1000 個任務(wù),而只有當(dāng)做一個同步阻塞的 run() 方法調(diào)用時才能滿足這個要求。
這事也讓我發(fā)現(xiàn)一個奇特的現(xiàn)象:就是網(wǎng)上幾乎沒人講過為什么在線程池里是 run 而不是 start,不知道是大家都覺得這是基操還是沒人仔細(xì)考慮過。總結(jié)
針對之前線上事故的總結(jié)上次已經(jīng)寫得差不多了,感興趣的可以翻回去看看。
這次呢可能更多是我自己的總結(jié),比如寫一篇技術(shù)博客時如果大部分人對某一個知識點(diǎn)討論的比較熱烈時,那一定是作者要么講錯了,要么沒講清楚。
這點(diǎn)確實(shí)是要把自己作為一個讀者的角度來看,不然很容易出現(xiàn)之前的一些誤解。
在這之外呢,我覺得對于線程池把這兩篇都看完同時也理解后對于大家理解線程池,利用線程池完成工作也是有很大好處的。
如果有在面試中加分的記得回來點(diǎn)贊、分享啊。
你的點(diǎn)贊與分享是對我最大的支持
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73923.html
摘要:前言前段時間寫過一篇線程池沒你想的那么簡單,和大家一起擼了一個基本的線程池,具備線程池基本調(diào)度功能。線程池自動擴(kuò)容縮容?;卣{(diào)以上就是線程池的構(gòu)造函數(shù)以及接口的定義。所以我們在使用線程池時,其中的任務(wù)一定要做好異常處理。線程異常捕獲的重要性。 showImg(https://segmentfault.com/img/remote/1460000019403163?w=1904&h=108...
摘要:如何優(yōu)雅的使用和理解線程池線程池中你不容錯過的一些細(xì)節(jié)由于篇幅限制,本次可能會分為上下兩篇。不接受新的任務(wù),同時等待現(xiàn)有任務(wù)執(zhí)行完畢后退出線程池。慎用方法關(guān)閉線程池,會導(dǎo)致任務(wù)丟失除非業(yè)務(wù)允許。前言 原以為線程池還挺簡單的(平時常用,也分析過原理),這次是想自己動手寫一個線程池來更加深入的了解它;但在動手寫的過程中落地到細(xì)節(jié)時發(fā)現(xiàn)并沒想的那么容易。結(jié)合源碼對比后確實(shí)不得不佩服 Doug Le...
摘要:如何優(yōu)雅的使用和理解線程池線程池中你不容錯過的一些細(xì)節(jié)由于篇幅限制,本次可能會分為上下兩篇。不接受新的任務(wù),同時等待現(xiàn)有任務(wù)執(zhí)行完畢后退出線程池。慎用方法關(guān)閉線程池,會導(dǎo)致任務(wù)丟失除非業(yè)務(wù)允許。 showImg(https://segmentfault.com/img/remote/1460000019230693); 前言 原以為線程池還挺簡單的(平時常用,也分析過原理),這次是想自...
摘要:月日,國家會議中心,由主辦的合稱將強(qiáng)勢登陸北京這是首次來華,在這場三合一的開源技術(shù)盛會中,來自國內(nèi)外的開發(fā)人員架構(gòu)師系統(tǒng)管理員專家商業(yè)領(lǐng)袖等數(shù)千名專業(yè)人士將匯聚一堂。后被收購,梁勝出任云平臺首席技術(shù)官,也成為首位華人。 6月19-20日,國家會議中心,由The Linux Foundation主辦的LinuxCon + ContainerCon + CloudOpen (合稱LC3) ...
閱讀 3360·2021-11-16 11:45
閱讀 4445·2021-09-22 15:38
閱讀 2876·2021-09-22 15:26
閱讀 3385·2021-09-01 10:48
閱讀 918·2019-08-30 15:56
閱讀 746·2019-08-29 13:58
閱讀 1544·2019-08-28 18:00
閱讀 2220·2019-08-27 10:53