關閉線程池我們可以選擇什么都不做,JVM 關閉時自然的會清除線程池對象。當然這么做,存在很大的弊端,線程池中正在執行執行的線程以及隊列中還未執行任務將會變得極不可控。所以我們需要想辦法控制到這些未執行的任務以及正在執行的線程。
線程池 API 提供兩個主動關閉的方
法 ThreadPoolExecutor#shutdownNow 與 ThreadPoolExecutor#shutdown,這兩個方法都可以用于關閉線程池,但是具體效果卻不太一樣。
一、線程池的狀態
在說線程池關閉方法之前,我們先了解線程池狀態。
線程池狀態關系圖如下:
從上圖我們看到線程池總共存在 5 種狀態,分別為:
- RUNNING:線程池創建之后的初始狀態,這種狀態下可以執行任務。
- SHUTDOWN:該狀態下線程池不再接受新任務,但是會將工作隊列中的任務執行結束。
- STOP: 該狀態下線程池不再接受新任務,但是不會處理工作隊列中的任務,并且將會中斷線程。
- TIDYING:該狀態下所有任務都已終止,將會執行 terminated() 鉤子方法。
- TERMINATED:執行完 terminated() 鉤子方法之后。
當我們執行 ThreadPoolExecutor#shutdown 方法將會使線程池狀態從 RUNNING 轉變為 SHUTDOWN。而調用 ThreadPoolExecutor#shutdownNow 之后線程池狀態將會從 RUNNING 轉變為 STOP。從上面的圖上還可以看到,當線程池處于 SHUTDOWN,我們還是可以繼續調用 ThreadPoolExecutor#shutdownNow 方法,將其狀態轉變為 STOP 。
二、ThreadPoolExecutor#shutdown
上面我們知道線程池狀態,這里先說說 shutdown 方法。shutdown 方法源碼比較簡單,能比較直觀理解其調用邏輯。
shutdown 方法源碼:
shutdown 方法首先加鎖,其次先檢查系統安裝狀態。接著就會將線程池狀態變為 SHUTDOWN,在這之后線程池不再接受提交的新任務。此時如果還繼續往線程池提交任務,將會使用線程池拒絕策略響應,默認情況下將會使用 ThreadPoolExecutor.AbortPolicy,拋出 RejectedExecutionException 異常。
interruptIdleWorkers 方法只會中斷空閑的線程,不會中斷正在執行任務的的線程。空閑的線程將會阻塞在線程池的阻塞隊列上。
線程池構造參數需要指定 coreSize(核心線程池數量),maximumPoolSize(最大的線程池數量),keepAliveTime(多余空閑線程等待時間),unit(時間單位),workQueue(阻塞隊列)。
當調用線程池的 execute 方法,線程池工作流程如下:
- 如果此時線程池中線程數量小于 coreSize,將會新建線程執行提交的任務。
- 如果此時線程池線程數量已經大于 coreSize,將會直接把任務加入到隊列中。線程將會從工作隊列中獲取任務執行。
- 如果工作隊列已滿,將會繼續新建線程。
- 如果工作隊列已滿,且線程數等于 maximumPoolSize,此時將會使用拒絕策略拒絕任務。
- 超過 coreSize 數量那部分線程,如果空閑了 keepAliveTime ,線程將會終止。
工作流程圖如下:
當線程池處于第二步時,線程將會使用 workQueue#take 獲取隊頭的任務,然后完成任務。如果工作隊列一直沒任務,由于隊列為阻塞隊列,workQueue#take 將會阻塞線程。
三、ThreadPoolExecutor#shutdownNow
ThreadPoolExecutor#shutdownNow 源碼如下:
shutdownNow 方法將會把線程池狀態設置為 STOP,然后中斷所有線程,最后取出工作隊列中所有未完成的任務返回給調用者。
對比 shutdown 方法,shutdownNow 方法比較粗暴,直接中斷工作線程。不過這里需要注意,中斷線程并不代表線程立刻結束。這里需要線程主動配合線程中斷響應。
線程中斷機制: thread#interrupt 只是設置一個中斷標志,不會立即中斷正常的線程。如果想讓中斷立即生效,必須在線程 內調用 Thread.interrupted() 判斷線程的中斷狀態。 對于阻塞的線程,調用中斷時,線程將會立刻退出阻塞狀態并拋出 InterruptedException 異常。所以對于阻塞線程需要正確處理 InterruptedException 異常。
awaitTermination
線程池 shutdown 與 shutdownNow 方法都不會主動等待執行任務的結束,如果需要等到線程池任務執行結束,需要調用 awaitTermination 主動等待任務調用結束。
調用方法如下:
如果線程池任務執行結束,awaitTermination 方法將會返回 true,否則當等待時間超過指定時間后將會返回 false。
如果需要使用這種進制,建議在上面的基礎上增加一定重試次數。這個真的很重要!!!
四、優雅關閉線程池
回顧上面線程池狀態關系圖,我們可以知道處于 SHUTDOWN 的狀態下的線程池依舊可以調用 shutdownNow。所以我們可以結合 shutdown , shutdownNow,awaitTermination ,更加優雅關閉線程池。