chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

原理解析:線程池中多余的線程是如何回收的?

Android編程精選 ? 來源:博客園 ? 作者:kingsleylam ? 2022-11-11 09:57 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

最近閱讀了JDK線程池ThreadPoolExecutor的源碼,對線程池執(zhí)行任務的流程有了大體了解,實際上這個流程也十分通俗易懂,就不再贅述了,別人寫的比我好多了。

不過,我倒是對線程池是如何回收工作線程比較感興趣,所以簡單分析了一下,加深對線程池的理解吧。

那么,就以JDK1.8為例分析吧。

1. runWorker(Worker w)

工作線程啟動后,就進入runWorker(Worker w)方法。

里面是一個while循環(huán),循環(huán)判斷任務是否為空,若不為空,執(zhí)行任務;若取不到任務,或發(fā)生異常,退出循環(huán),執(zhí)行processWorkerExit(w, completedAbruptly); 在這個方法里把工作線程移除掉。

取任務的來源有兩個,一個是firstTask,這個是工作線程第一次跑的時候執(zhí)行的任務,最多只能執(zhí)行一次,后面得從getTask()方法里取任務??磥?,getTask()是關鍵,在不考慮異常的場景下,返回null,就表示退出循環(huán),結束線程。下一步,就得看看,什么情況下getTask()會返回null。

篇幅有限,分段截取,省略中間執(zhí)行任務的步驟

3cc3b850-60f7-11ed-8abf-dac502259ad0.png

3cdade5e-60f7-11ed-8abf-dac502259ad0.png

2. getTask() 返回null

一共有兩種情況會返回null,見紅框處 。

第一種情況,線程池的狀態(tài)已經(jīng)是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列為空;

第二種情況,工作線程數(shù)已經(jīng)大于最大線程數(shù)或當前工作線程已超時,且,還有其他工作線程或任務隊列為空。這點比較難理解,總之先記住,后面會用。

下面以條件1和條件2分別指代這兩種情況的判斷條件。

3cf82a72-60f7-11ed-8abf-dac502259ad0.png

3. 分場景分析線程池回收工作線程

3.1 未調用shutdown() ,RUNNING狀態(tài)下全部任務執(zhí)行完成的場景

這種場景,會將工作線程的數(shù)量減少到核心線程數(shù)大?。ㄈ绻緛砭蜎]有超過,則不需要回收)。

比如一個線程池,核心線程數(shù)為4,最大線程數(shù)為8。一開始是4個工作線程,當任務把任務隊列塞滿,就得將工作線程增加到8. 當后面任務執(zhí)行到差不多了,線程取不到任務了,就會回收到4個工作線程的狀態(tài)(取決于allowCoreThreadTimeOut的值,這里討論默認值false的情況,即核心線程不會超時。如果為true,工作線程可以全部銷毀)。

可以先排除上面提到的條件1,線程池的狀態(tài)已經(jīng)是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列為空。因為線程池一直是RUNNING,這條判斷永遠是false。在這個場景中,可以當條件1不存在。

下面分析取不出任務時線程是怎么運行的。

step1. 從任務隊列取任務有兩種方式,超時等待還是可以一直阻塞下去。決定因素是timed變量。該變量在前面賦值,如果當前線程數(shù)大于核心線程數(shù),變量timed為true, 否則為false(上面說了,這里只討論allowCoreThreadTimeOut為false的情況)。很明顯,現(xiàn)在討論的是timed為true的情況。keepAliveTime一般不設置,默認值為0,所以基本上可以認為是不阻塞,馬上返回取任務的結果。

在線程超時等待喚醒之后,發(fā)現(xiàn)取不出任務,timeOut變?yōu)閠rue,進入下一次循環(huán)。

step2. 來到條件1的判斷,線程池一直RUNNING, 不進入代碼塊。

step3. 來到條件2的判斷,這時任務隊列為空,條件成立,CAS減少線程數(shù),若成功,返回null,否則,重復step1。

這里要注意,有可能多條線程同時通過條件2的判斷,那會不會減少后線程的數(shù)量反而比預想的核心線程數(shù)少呢?

比如當前線程數(shù)已經(jīng)只有5條了,此時有兩條線程同時喚醒,通過條件2的判斷,同時減少數(shù)量,那剩下的線程數(shù)反而只有3條,和預期不一致。

實際上是不會的。為了防止這種情況,compareAndDecrementWorkerCount(c) 用的是CAS方法,如果CAS失敗就continue,進入下一輪循環(huán),重新判斷。

像上述例子,其中一條線程會CAS失敗,然后重新進入循環(huán),發(fā)現(xiàn)工作線程數(shù)已經(jīng)只有4了,timed為false, 這條線程就不會被銷毀,可以一直阻塞了(workQueue.take())。

這一點我思考了很久才得出答案,一直在想沒有加鎖的情況下是怎么保證一定能不多不少回收到核心線程數(shù)的呢。原來是CAS的奧妙。

從這里也可以看出,雖然有核心線程數(shù),但線程并沒有區(qū)分是核心還是非核心,并不是先創(chuàng)建的就是核心,超過核心線程數(shù)后創(chuàng)建的就是非核心,最終保留哪些線程,完全隨機。

3.2 調用shutdown() ,全部任務執(zhí)行完成的場景

這種場景,無論是核心線程還是非核心線程,所有工作線程都會被銷毀。

在調用shutdown()之后,會向所有的空閑工作線程發(fā)送中斷信號。

3d109dd2-60f7-11ed-8abf-dac502259ad0.png

最終傳入false,調用下面這個方法。

3d2f2572-60f7-11ed-8abf-dac502259ad0.png

可以看出,在發(fā)出中斷信號前,會判斷是否已經(jīng)中斷,以及要獲得工作線程的獨占鎖。

發(fā)出中斷信號的時候,工作線程要么在getTask()里準備獲取任務,要么在執(zhí)行任務,那就得等它執(zhí)行完當前任務才會發(fā)出,因為工作線程在執(zhí)行任務的時候,也會工作線程加鎖。工作線程執(zhí)行完任務,又跑到getTask()里面去了。

所以我們只要看getTask()里面怎么應對中斷異常的就可以了。

3d51a8a4-60f7-11ed-8abf-dac502259ad0.png

工作線程在getTask()里,有兩種可能。

3.2.1 任務已全部完成,線程在阻塞等待。

很簡單,中斷信號將其喚醒,從而進入下一輪循環(huán)。到達條件1處,符合條件,減少工作線程數(shù)量,并返回null,由外層結束這條線程。

這里的decrementWorkerCount()是自旋式的,一定會減1。

3d6c384a-60f7-11ed-8abf-dac502259ad0.png

3.2.2 任務還沒有完全執(zhí)行完

調用shutdown()之后,未執(zhí)行完的任務要執(zhí)行完畢,池子才能結束。所以此時有可能線程還在工作。

這里又要分兩個階段討論

階段1 任務較多,工作線程都能獲得任務

這里還不涉及到線程退出,可以跳過不看,只是分析一下收到中斷信號后線程的表現(xiàn)。

假設有線程A,正通過getTask()里獲取任務。此時A被中斷,在獲取任務時,無論是poll()還是take(),都會拋出中斷異常。異常被捕獲,重新進入下一輪循環(huán),只要隊列不為空,就可以繼續(xù)取任務。

線程A被中斷,再次取任務,調用workQueue.poll() or workQueue.take(),不會拋出異常嗎?還可以正常取出任務嗎?

這就要看workQueue的實現(xiàn)了。workQueue是BlockingQueue類型,以常見的LinkedBlockingQueue和ArrayBlockingQueue為例,加鎖時都是調用lockInterruptibly(),是響應中斷的。該方法又調用了AQS的acquireInterruptibly(int arg)。

acquireInterruptibly(int arg),無論是在入口處判斷中斷異常,還是在parkAndCheckInterrupt()方法阻塞,被中斷喚醒并判斷中斷異常時,均使用了Thread.interrupted()。這個方法會返回線程的中斷狀態(tài),并把中斷狀態(tài)重置!也就是說,線程不再是中斷狀態(tài)了,這樣在再次取任務時,就不會報錯了。

因此,這對于正在準備取任務的線程,只是相當于浪費了一次循環(huán),這可能是線程中斷帶來的副作用吧,當然,對整體的運行不影響。

分析到這里,我不禁感嘆,這里BlockingQueue剛好是會重置中斷狀態(tài),這到底是怎么想出來的絕妙設計?。緿oug Lea大神Orz。

3d7e4dd2-60f7-11ed-8abf-dac502259ad0.png

3d9fb242-60f7-11ed-8abf-dac502259ad0.png

階段2 任務剛好要執(zhí)行完了

這時任務已經(jīng)快取完了,比如有4條工作線程,只剩下2個任務,那就可能出現(xiàn)2條線程獲得任務,2條線程阻塞。

因為在獲取任務前的判斷,沒有加鎖,那么會不會出現(xiàn),所有線程都通過了前面的校驗,來到workQueue獲取任務的地方,剛好任務隊列已經(jīng)空了,線程全部阻塞了呢?因為shutdown() 已經(jīng)執(zhí)行完畢,無法再向線程發(fā)出中斷信號,從而線程一直在阻塞,無法被回收。

這種是不會發(fā)生的。

假設有A,B,C,D四條工作線程,同時通過了條件1和條件2的判斷,來到取任務的地方。那么,工作隊列至少還有一個任務,至少會有一條線程能取到任務。

假設A,B獲得了任務,C,D阻塞。

A, B接下來的步驟是:

step1. 任務執(zhí)行完成后,再次getTask(),此時符合條件1,返回null,線程準備被回收。

step2. processWorkerExit(Worker w, boolean completedAbruptly) 將線程回收。

回收就只是把線程干掉這么簡單嗎?來看看processWorkerExit(Worker w, boolean completedAbruptly) 的方法。

3dbb9304-60f7-11ed-8abf-dac502259ad0.png

可以看到,在里面除了workers.remove(w) 移除線,還調用了tryTerminate()。

3dde60b4-60f7-11ed-8abf-dac502259ad0.png

第一個判斷條件沒有一個子條件符合,跳過。第二個條件,工作線程還存在,那么隨機中斷一條空閑線程。

那么問題就來了,中斷一條空閑線程,也沒說是一定中斷正在阻塞的線程啊。如果A, B同時退出,有沒有可能出現(xiàn)A中斷B, B中斷A,AB互相中斷,從而沒有線程去中斷喚醒阻塞的線程呢?

答案仍然是,想多了……

假設A能走到這里,說明A已經(jīng)從工作線程的集合workers里面移除了(processWorkerExit(Worker w, boolean completedAbruptly) 在tryTerminate()之前,已經(jīng)將其移除)。那么A中斷B,B來到這里中斷,就不會在workers里面找到A了。

3df2dd64-60f7-11ed-8abf-dac502259ad0.png

也就是說,退出的線程不能互相中斷,我從集合中退出后,中斷了你,你不能中斷我,因為我已經(jīng)退出集合,你只能中斷別人。那么,即使有N個線程同時退出,至少在最后,也會有一條線程,會中斷剩余的阻塞線程。

就像多米諾骨牌一樣,中斷信號就會被傳播下去。

阻塞的C,D中的任意一條被中斷喚醒后,又會重復step1的動作,周而復始,直到所有阻塞線程都被中斷,喚醒。

這也是為什么在tryTerminate()里面,傳入false,只需要中斷任意一條空閑線程的原因。

想到這里,再次對Doug Lea心生欽敬(粵語)之情。這設計得也太妙了叭。

4. 總結

ThreadPoolExecutor回收工作線程,一條線程getTask()返回null,就會被回收。

分兩種場景。

1) 未調用shutdown() ,RUNNING狀態(tài)下全部任務執(zhí)行完成的場景

線程數(shù)量大于corePoolSize,線程超時阻塞,超時喚醒后CAS減少工作線程數(shù),如果CAS成功,返回null,線程回收。否則進入下一次循環(huán)。當工作者線程數(shù)量小于等于corePoolSize,就可以一直阻塞了。

2) 調用shutdown() ,全部任務執(zhí)行完成的場景

shutdown() 會向所有線程發(fā)出中斷信號,這時有兩種可能。

2.1)所有線程都在阻塞

中斷喚醒,進入循環(huán),都符合第一個if判斷條件,都返回null,所有線程回收。

2.2)任務還沒有完全執(zhí)行完

至少會有一條線程被回收。在processWorkerExit(Worker w, boolean completedAbruptly)方法里會調用tryTerminate(),向任意空閑線程發(fā)出中斷信號。所有被阻塞的線程,最終都會被一個個喚醒,回收。

這一次的分析,昨晚開始寫,寫到一半卡殼,今天早上接著寫,前后花了大概2+2=4個小時寫博客以及1小時思考。

說實話自己還是有點亂,無法一下子理解透徹,也不知道自己理解得對不對。

有沒有用,我也不知道,只能說,加深了對線程池的理解吧(安慰自己),同時也感慨設計之精妙。

如有不正確的地方,請大家指正。



審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 源碼
    +關注

    關注

    8

    文章

    671

    瀏覽量

    30348
  • 線程池
    +關注

    關注

    0

    文章

    57

    瀏覽量

    7138
  • JDK
    JDK
    +關注

    關注

    0

    文章

    83

    瀏覽量

    16893

原文標題:原理解析:線程池中多余的線程是如何回收的?

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    線程編程之Linux線程編程

    9.2 Linux線程編程 9.2.1 線程基本編程 這里要講的線程相關操作都是用戶空間中的線程的操作。在Linux中,一般pthread線程
    發(fā)表于 10-18 15:55 ?3次下載

    關于進程與線程解析PDF文件資料

    電子發(fā)燒友為你提供關于進程與線程解析PDF文件資料免費下載
    發(fā)表于 11-25 10:42 ?11次下載
    關于進程與<b class='flag-5'>線程</b>的<b class='flag-5'>解析</b>PDF文件資料

    Linux應用開發(fā)【第五章】線程編程應用開發(fā)

    5.1.6 線程的退出與回收 5.2 線程的控制 5.2.1 多線程編臨界資源訪問 5.2.2 互斥鎖API簡述 5.2.3 多線程編執(zhí)行
    的頭像 發(fā)表于 12-10 19:15 ?785次閱讀
    Linux應用開發(fā)【第五章】<b class='flag-5'>線程</b>編程應用開發(fā)

    線程池中如何獲取和處理異常

    在實際開發(fā)中,我們常常會用到線程池,但任務一旦提交到線程池之后,如果發(fā)生異常之后,怎么處理?
    的頭像 發(fā)表于 10-24 15:44 ?1701次閱讀

    面試官:線程池中多余線程是如何回收的?

    最近閱讀了JDK線程池ThreadPoolExecutor的源碼,對線程池執(zhí)行任務的流程有了大體了解,實際上這個流程也十分通俗易懂,就不再贅述了,別人寫的比我好多了。
    的頭像 發(fā)表于 11-07 10:46 ?1845次閱讀

    什么是線程線程池中線程實現(xiàn)復用的原理

    一般建議自定義線程工廠,構建線程的時候設置線程的名稱,這樣就在查日志的時候就方便知道是哪個線程執(zhí)行的代碼。
    發(fā)表于 01-29 13:44 ?1968次閱讀

    線程線程

    線程池通常用于服務器應用程序。 每個傳入請求都將分配給線程池中的一個線程,因此可以異步處理請求,而不會占用主線程,也不會延遲后續(xù)請求的處理
    的頭像 發(fā)表于 02-28 09:53 ?1116次閱讀
    多<b class='flag-5'>線程</b>之<b class='flag-5'>線程</b>池

    Java線程池核心原理

    看過Java線程池源碼的小伙伴都知道,在Java線程池中最核心的類就是ThreadPoolExecutor,
    的頭像 發(fā)表于 04-21 10:24 ?1143次閱讀

    如何理解線程安全?

    本次分享線程安全的基礎知識。
    的頭像 發(fā)表于 05-08 15:03 ?1056次閱讀
    如何<b class='flag-5'>理解</b><b class='flag-5'>線程</b>安全?

    什么是線程安全?如何理解線程安全?

    在多線程編程中,線程安全是必須要考慮的因素。
    的頭像 發(fā)表于 05-30 14:33 ?2472次閱讀
    什么是<b class='flag-5'>線程</b>安全?如何<b class='flag-5'>理解</b><b class='flag-5'>線程</b>安全?

    核心線程數(shù)和最大線程數(shù)區(qū)別

    核心線程數(shù)和最大線程數(shù)區(qū)別 核心線程數(shù)是線程池中一直存在的線程數(shù),不會被
    的頭像 發(fā)表于 06-01 09:33 ?8094次閱讀

    cpu核心數(shù)和線程數(shù)的關系

    核心線程數(shù)是線程池中一直存在的線程數(shù),不會被回收。最大線程數(shù)是
    的頭像 發(fā)表于 06-01 17:41 ?1.1w次閱讀

    線程池的線程怎么釋放

    線程分組看,pool名開頭線程占616條,而且waiting狀態(tài)也是616條,這個點就非??梢闪耍覕喽ň褪沁@個pool開頭線程池導致的問題。我們先排查為何這個線程
    發(fā)表于 07-31 10:49 ?2564次閱讀
    <b class='flag-5'>線程</b>池的<b class='flag-5'>線程</b>怎么釋放

    核心線程數(shù)和最大線程數(shù)怎么設置

    核心線程數(shù)和最大線程數(shù)是Java線程池中重要的參數(shù),用來控制線程池中線程的數(shù)量和行為。正確地設置
    的頭像 發(fā)表于 12-01 13:50 ?1.1w次閱讀

    線程池七大核心參數(shù)執(zhí)行順序

    線程池是一種用于管理和調度線程執(zhí)行的技術,通過將任務分配到線程池中線程進行處理,可以有效地控制并發(fā)線程
    的頭像 發(fā)表于 12-04 16:45 ?1538次閱讀