https://blog.csdn.net/zaozi/article/details/38854561
https://blog.csdn.net/z69183787/article/details/48683965
前言
最近在使用ExecutorService的時候,對於與ExecutorService相關的概念有些迷糊,
加上本身ExecutorService內部的有些方法名在取名上也容易讓使用者誤解,導致
犯了一些錯誤。在解決的過程中,偶爾看到了日本人寫的一篇文章簡單明了,通俗易懂
所以想着翻譯成中文希望能夠幫助到與我有一樣困惑的程序員朋友們。
原文地址如下:
http://gurimmer.lolipop.jp/daihakken/2012/01/27/javaexecutorserviceの正しい終了shutdownの仕方/
閑話少說,文章如下
雖然使用ExecutorService可以讓線程處理變的很簡單,
可是有沒有人覺得在結束線程運行時候只調用shutdown方法就可以了?
實際上,只調用shutdown方法的是不夠的。
我們用學校的老師和學生的關系來說明這個問題。
shutdown只是起到通知的作用
我們來假設如下場景:
學校里在課上老師出了一些問題安排全班同學進行解答並對學生說“開問題解答完畢后請舉手示意!”
如果有學生解答完畢后會舉手對老師說“老師我做完了!”,如果大家都解題完畢后上課結束。
上面的場景對應於ExecutorService里的方法的話是下面的樣子。
老師: ExecutorService
學生: ExecutorService里的線程
問題: 通過參數傳遞給ExecutorService.execute的任務(Runnable)
授課: main線程
學校: Java進程
“問題解答完畢后請舉手示意!”是shutdown方法。“老師我做完了!”是各個任務(Runnable)的運行結束。
所有的任務(Runnable)都結束了的話main線程(授課)也結束了。
在這里,我們假設試卷中有難度較大的問題,當然學生解答較難的問題也會比較花時間。
在上面的場景中老師除了shutdown方法之外什么也做不了,只能呆呆得等着學生們說,“老師我做完了!”之后才可以有下一步動作。
這都是因為shutdown方法只是用來通知的方法。
這時如果即使授課時間結束(main線程結束),學校也不能放學(Java進程結束),因為學生們還在解題中呢。這個時候如果你是老師你會怎么做?
一般的情況肯定是經過一定的時間在授課快要結束的時候,如果還有人沒有解答出來的話,或者公布給大家解題方法,
或者作為課后習題讓學生回去繼續思考,然后結束上課對不對!
定好下課時間后等待結束
如果經過了一定的時間任務(Runnable)還不結束的時候我們可以通過中止任務(Runnable)的執行,以防止一直等待任務的結束。
awaitTermination方法正是可以實現這個中止作用的角色。
具體的使用方法是,在shutdown方法調用后,接着調用awaitTermination方法。這時只需要等待awaitTermination方法里第一個參數指定的時間。
如果在指定的時間內所有的任務都結束的時候,返回true,反之返回false。返回false意味着課程結束的時候還有題目沒有解答出來的學生。
通過shutdownNow方法,我們可以作為老師向同學發出“沒有解答出來的同學明天給出解答”的命令后結束授課。
shutdownNow方法的作用是向所有執行中的線程發出interrupted以中止線程的運行。這時,各個線程會拋出InterruptedException異常(前提是
線程中運行了sleep等會拋出異常的方法)
所以正確的中止線程的方法如下:
-
public static void main(String[] args) {
-
-
ExecutorService pool = Executors.newFixedThreadPool( 5);
-
final long waitTime = 8 * 1000;
-
final long awaitTime = 5 * 1000;
-
-
Runnable task1 = new Runnable(){
-
public void run(){
-
try {
-
System.out.println( "task1 start");
-
Thread.sleep(waitTime);
-
System.out.println( "task1 end");
-
} catch (InterruptedException e) {
-
System.out.println( "task1 interrupted: " + e);
-
}
-
}
-
};
-
-
Runnable task2 = new Runnable(){
-
public void run(){
-
try {
-
System.out.println( " task2 start");
-
Thread.sleep( 1000);
-
System.out.println( " task2 end");
-
} catch (InterruptedException e) {
-
System.out.println( "task2 interrupted: " + e);
-
}
-
}
-
};
-
// 讓學生解答某個很難的問題
-
pool.execute(task1);
-
-
// 生學生解答很多問題
-
for(int i=0; i<1000; ++i){
-
pool.execute(task2);
-
}
-
-
try {
-
// 向學生傳達“問題解答完畢后請舉手示意!”
-
pool.shutdown();
-
-
// 向學生傳達“XX分之內解答不完的問題全部帶回去作為課后作業!”后老師等待學生答題
-
// (所有的任務都結束的時候,返回TRUE)
-
if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){
-
// 超時的時候向線程池中所有的線程發出中斷(interrupted)。
-
pool.shutdownNow();
-
}
-
} catch (InterruptedException e) {
-
// awaitTermination方法被中斷的時候也中止線程池中全部的線程的執行。
-
System.out.println( "awaitTermination interrupted: " + e);
-
pool.shutdownNow();
-
}
-
-
System.out.println( "end");
-
}
可以看出上面程序中waitTime的值比awaitTime大的情況下,發生Timeout然后執行中的線程會中止執行而結束。
反過來如果縮小waitTime的值,增大awaitTime的值的的話,各個線程就會不被中止的正常運行至結束。
在這里,如果我們把awaitTime和shutdownNow方法全部屏蔽掉的只留下shutdown方法的話會怎樣呢?
會變成表示main方法結束的「end」顯示出來之后,會打印出很多的task2的start和end。
這就是雖然課程結束了,但是學校仍然不能放學的不正常狀態。最惡劣的情況會導致JAVA進程一直殘留在OS中。
所以我們一定不要忘記使用awaitTermination和shutdownNow
shutdown也是很重要的
看了上面的描述后可能有些人會認為,只需要執行awaitTermination和shutdownNow就可以正常結束線程池中的線程了。其實不然。
shutdown方法還有「大家只解答我要求的問題,其它的不用多做」的意思在里面。
shutdown方法調用后,就不能再繼續使用ExecutorService來追加新的任務了,如果繼續調用execute方法執行新的任務的話
就會拋出RejectedExecutionException異常。(submit方法也會拋出上述異常)
而且,awaitTermination方法也不是在它被調用的時間點上簡單得等待任務結束而是在awaitTermination方法調用后,
持續監視各個任務的狀態以或者是否線程已經運行結束。所以不調用shutdown方法執行調用awaitTermination的話由於追加出來的任務可能
會導致任務狀態監視出現偏差而發生預料之外的awaitTermination的Timeout異常
正確的調用順序是
shutdown方法
awaitTermination方法
shutdownNow方法(發生異常或者是Timeout的時候)
實際開發的系統可能會有不能強制線程中止執行的場景出現,所以雖然推薦使用上面說的調用順序但也並不是絕對一成不變的。
另外,可以經過一定時間間隔而有計划調用任務執行的ScheduledExecutorService同樣適用於上面說的調用順序,但是在使用scheduled方法的時候需要另外一些步驟。
可以關閉 ExecutorService,這將導致其拒絕新任務。提供兩個方法來關閉 ExecutorService。shutdown() 方法在終止前允許執行以前提交的任務,而 shutdownNow() 方法阻止等待任務啟動並試圖停止當前正在執行的任務。在終止時,執行程序沒有任務在執行,也沒有任務在等待執行,並且無法提交新任務。應該關閉未使用的 ExecutorService 以允許回收其資源。
下列方法分兩個階段關閉 ExecutorService。第一階段調用 shutdown 拒絕傳入任務,然后調用 shutdownNow(如有必要)取消所有遺留的任務:
- void shutdownAndAwaitTermination(ExecutorService pool) {
- pool.shutdown(); // Disable new tasks from being submitted
- try {
- // Wait a while for existing tasks to terminate
- if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
- pool.shutdownNow(); // Cancel currently executing tasks
- // Wait a while for tasks to respond to being cancelled
- if (!pool.awaitTermination(60, TimeUnit.SECONDS))
- System.err.println("Pool did not terminate");
- }
- } catch (InterruptedException ie) {
- // (Re-)Cancel if current thread also interrupted
- pool.shutdownNow();
- // Preserve interrupt status
- Thread.currentThread().interrupt();
- }
- }
shutdown調用后,不可以再submit新的task,已經submit的將繼續執行。
shutdownNow試圖停止當前正執行的task,並返回尚未執行的task的list