線程池中的 工作線程如何被回收


前言

    JDK中的ThreadPoolExecutor線程池相信大家都很熟悉,對於線程池的一些高頻面試題,比如有哪些參數,每個參數的含義,什么時候發揮作用,工作流程等問題都能回答上來。而對於一些不是很常見的線程池面試題就顯得有點模糊,比如:線程池中線程執行完了一個任務接下來是做什么,是等待還是被收回,如果是等待,那么判斷的依據是啥,如果是被回收,那么是怎么被回收的。對於這些問題我們就必須深挖ThreadPoolExecutor源碼知識了,而不是背幾個常見的面試題就行,下面我們一起來看一下線程執行完了一個任務接下來是做什么


1. execute(Runnable command)

    execute方法是我們使用線程池的源頭,我來看一下調用execute方法后干了什么

重點看addWorker()方法,這里我們要聯想到線程池是一個池子,從方法名也可以看出addWorker()增加一個工作者,那么這個Worker對象肯定會放到池子里面去,我們能想到這個池子肯定是一個集合,比如List,Set,Map都可以

果然是用Set做集合,然后會把Worder對象放到Set中,然后我們再來看是怎么向線程池中添加任務的。

我們會看到將傳進來的Runnable任務包裝成一個Worker對象,然后將Worker的thread成員屬性復制給了Thread t局部變量,我們具體看一下Worker這個類

會發現Worker的構造方法會利用創建線程工廠創建一個新線程,並且將當前this對象賦值給了thread成員變量,重寫了run()方法,也至於上一張圖所指出的thread.start()啟動線程會調用Worker對象run()方法,后面重點來看run()方法


2. runWorker(Worker w)

    從源碼上可以看到重寫的run()方法主要調用的就是runWorker方法

可以看到runWorker方法中,首先會將firstTask賦值給Runnable task變量,firstTask是工作線程第一次跑的時候執行的任務,然后隨后就將firstTask=null,即firstTask只會執行一次。接下來就進入while條件循環,如果while條件循環不滿足,最后就會進入到finally中的processWorkerExit線程退出,也就從線程池Set中remove線程。所以我們可以把重點關注在while的條件上,第一個條件task != null是用來判斷第一次跑的任務,后面的getTask()方法里獲取任務,接下來就看getTask()方法,看看getTask()方法什么時候給我們返回null,就代表着這個while條件結束,也就會進入到我們的processWorkerExit()線程退出方法

我們可以看到有兩個地方返回null,一個是當線程池的狀態是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列為空,那么就會返回null,decrementWorkerCount()將工作線程數量減1。這里暫時先不考慮線程池中止狀態,先假設線程池一直都是RUNNING狀態,那么就會進入到第二個返回null的判斷條件。第二個地方返回null的條件是:1. 線程池中的工作線程數量 > 最大線程數 or (大於核心線程數 and 工作線程存活時間已經超時) 2. 線程數 > 1 or 隊列已經為空 同時滿足條件1,2時,再調用CAS扣減線程數,這里解釋一下為什么需要用到CAS扣減線程,因為防止兩個線程同時滿足條件,然后扣減線程數,這樣會導致線程數變少。比如核心線程數為4,當前線程數有5個,然后有兩個線程判斷條件,發現同時滿足,則兩個線程都會進行扣減,變成3,本來應該保持核心線程數為4的。所以采用CAS操作來扣減線程達到核心線程數,如果扣減成功,則返回null,也就會結束runWorker()方法中的while條件。然后進入finally區域,退出線程

    前面分析到第二個地方返回null指的是線程正常執行完一個任務,然后從getTask()獲取任務,當獲取任務為null時,就會退出線程了,從線程池中刪除。下面來分析一下線程池狀態為STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列為空時,返回null的情況。


3.調用shutdown(),來終止線程

    當調用shutdown()方法時,線程池會等待正在執行任務的線程並且需要將阻塞隊列中的任務執行完再進行銷毀,並且不再接受新任務。跟shutdownNow()方法不同的是,shutdownNow方法不管是正在執行還是空閑的線程都會進行中斷,返回阻塞隊列中未完成的任務,阻塞隊列中的元素也就不會再執行了

這里可能網友會有疑問了,怎么判斷是空閑線程的???

我們可以看到interruptIdleWorkers方法,向線程發出中斷信號前,需要獲得tryLock()獲取獨占鎖,才能執行t.interrupt()方法,我們再返過頭來看runWorker()方法

可以看到如果getTask()返回不為null,則會執行任務,則會拿到獨占鎖,那么對於正在執行任務的線程是無法發出中斷信息號的。除非任務執行完,釋放鎖。那么interruptIdleWorkers中的w.tryLock()才能拿到鎖,發出中斷信號

    3.1任務剛好執行完任務,釋放鎖,進入到while條件語句

再次進入到while的判斷條件里面去。我們再來看getTask()方法第一個地方返回null的地方

返回null的條件需要兩個,一個是shutdown狀態,還有一個是隊列為空,沒有任務(暫時不考慮stop狀態)。如果隊列中還存在任務,則會調用poll或者take獲取任務

這里網友可能會想到線程不是已經是中斷狀態了嗎?那么調用poll和take方法時,應該會一直拋出InterruptedException異常才對。如果對AQS比較熟悉的話,應該可以想到不會出現這種情況,我們再來看poll和task調用的方法

雖然是會拋出中斷異常,但是線程中斷狀態會被重置,也就是下次線程不是中斷狀態,可以繼續獲取任務執行,直到隊列為空。隊列為空,線程池狀態又為shutdown狀態,最后會返回null。

    3.2線程如果本身是阻塞狀態

如果線程本身在獲取任務是阻塞住了,然后shutdown發出中斷信號,拋出中斷異常,再次進入到第一個地方判斷條件,那么邏輯就跟3.1類似,會判斷隊列是否是空的,如果是非空的,那么繼續會調用poll和take獲取任務,首次去獲取的時候線程的中斷狀態也會被重置,下次就能正常的獲取任務,直到隊列為空,線程池狀態為shutdown狀態,返回null


4.總結

    總的來說,ThreadPoolExecutor回收線程都是等getTask()獲取不到任務,返回null時,調用processWorkerExit方法從Set集合中remove掉線程,getTask()返回null又分為2兩種場景:

        1. 線程正常執行完任務,並且已經等到超過keepAliveTime時間,大於核心線程數,那么會返回null,結束外層的runWorker中的while循環

        2. 當調用shutdown()方法,會將線程池狀態置為shutdown,並且需要等待正在執行的任務執行完,阻塞隊列中的任務執行完才能返回null


免責聲明!

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



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