並發編程-線程池(二)線程池回收線程


【1】https://blog.csdn.net/u013256816/article/details/109213183

  面試 鵝廠 的時候,問到了 線程池如何銷毀線程,這題答的不好。

  這個問題考察的是對線程池的理解,在既然了解了線程池在什么時候創建線程。

  那么也要了解線程池在不同情況是如何回收線程,什么時候回收,怎么回收。

 

一、線程池狀態和狀態轉換

  首先要了解線程池狀態和線程池狀態之間的轉換 https://www.cnblogs.com/Jomini/p/13669993.html

 

Running : 線程池的初始化狀態是RUNNING, 線程池處在RUNNING狀態時,能夠接收新任務,以及對已添加的任務進行處理。

SHUTDOWN線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務,異步中斷閑置的的線程

       調用線程池的 shutdown() 接口時,線程池由RUNNING -> SHUTDOWN。  

STOP : 線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。 

    用線程池的 shutdownNow() 接口時,線程池由 (RUNNING or SHUTDOWN ) -> STOP。

    (注: shutdown 和 shutdownNow() 的不同)

TIDYING當所有的任務已終止,ctl記錄的”任務數量”為0,線程池會變為TIDYING狀態。當線程池變為TIDYING狀態時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變為TIDYING時,進

行相應的處理;可以通過重載terminated()函數來實現。 

  當線程池在SHUTDOWN狀態下,阻塞隊列為空並且線程池中執行的任務也為空時,就會由 SHUTDOWN -> TIDYING。

  當線程池在STOP狀態下,線程池中執行的任務為空時,就會由STOP -> TIDYING。

TERMINATED線程池徹底終止,就變成TERMINATED狀態。 線程池處在TIDYING狀態時,執行完 terminated()之后,就會由 TIDYING -> TERMINATED。

 

二、線程池回收線程

  首先分為 不調用 shutdown調用 shutdown 的場景,shutdownNow 這里不分析。

   然后分別,分析 Running狀態, Shutdown狀態, Stop 狀態, 下的回收線程。

2.1、不調用 shutdown 的場景下

2.1.1、Running 狀態

  [1]、Running狀態下,當前工作線程數量多於核心線程,且任務隊列為空

  當前線程數量多於核心線程,且任務隊列為空,此時滿足減少線程數量的判斷,並減少一個線程數量,線程池再回收移除一個線程。

  [1.1]、 jdk 1.8源碼的具體實現【1】

runWorker(Worker w) 方法

  工作線程啟動后,就進入 runWorker(Worker w) 方法,也就是 Running 狀態下,在runWorker(Worker w)) 中

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

  取任務的來源有兩個,一個是firstTask,這個是工作線程第一次跑的時候執行的任務,最多只能執行一次,后面得從 getTask() 方法里取任務。

  getTask() 是關鍵,在不考慮異常的場景下,返回null,就表示退出循環,結束線程。

 

   

getTask() 方法

    getTask() 返回null, 有以下兩個條件

    第一種情況,線程池的狀態已經是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列為空;

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

    在 Running 狀態下,是在滿足第二種情況,才會返回 null。 

    此時,當前線程數量多於核心線程,則 timed 為 true; 並且,任務隊列為空,workQueue.isEmpty() is ture。

    根據下圖的源碼第二部分,以上條件滿足返回 null 的條件,並通過 compareAndDecrementWorkerCount(c) 方法,通過 CAS 機制減少一個線程數量,注意線程還未被回收。

    如果compareAndDecrementWorkerCount(c) 失敗,則 continue 進入下一次循環

   

  processWorkerExit(Worker w, boolean completedAbruptly) 方法

    在 runWorker(Worker w) 方法中,getTask 返回 null 后,就可以進入 processWorkerExit 回收線程。

    通過 workers.remove(w) 移除線程,並用了 tryTerminate()

   

tryTerminate()

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

  

 

 

InterruptdleWorkers(boolean onlyOne)

   被移除出工作隊列的線程,中斷其他線程。

  

2.1.1.2、線程數量大於最大線程數量,多的直接丟棄。

 

2.2、調用shutdown 的場景下

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

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

  

 

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

  

2.2.1 Running 狀態

  2.2.1.1、 Running 狀態,任務已全部完成,線程在阻塞等待。在 Running 狀態進入 shutdown 狀態。

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

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

    

 

   2.2.1.2、 Running 狀態,任務還沒有完全執行完

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

    這里又要分兩個階段討論

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

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

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

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

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

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

樣在再次取任務時,

  就不會報錯了。

    

 

   2.2.1.2-2、階段2 任務剛好要執行完了

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

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

   A, B接下來的步驟是:

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

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

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

  

2.2.2、Shutdown 狀態

  線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務,且異步中斷閑置的的線程

  getTask 返回null, 調用 processWorkerExit(w, completedAbruptly); 移除線程;

  當所有的任務已終止,ctl記錄的”任務數量”為0,線程池會變為TIDYING狀態。接着會執行terminated()函數。

  執行完 terminated() 之后,就會由 TIDYING -> TERMINATED,線程池被設置為TERMINATED狀態。

2.2.3、Stop 狀態

  線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。 

 

2.3、shutdown 和 shutdownNow 區別 : https://blog.csdn.net/horero/article/details/77622951

 

四、線程池回收線程源碼分析

  https://blog.csdn.net/u013256816/article/details/109213183

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM