Java中如何正確的關閉線程池ExecutorService

概念

ExecutorService關於關閉主要有如下幾個方法

  • shutdown:在線程池隊列中的提交的任務會執行,無法提交新的任務,注意調用這個方法,線程池不會等待(wait)在執行的任務執行完成,可以使用awaitTermination實現這個目的。這裏需要注意的是:在執行的任務因爲是異步線程執行的,任務還是會繼續執行,只是說線程池不會阻塞等待任務執行完成
  • List<Runnable> shutdownNow():試圖關閉正在執行的任務,不會執行已經提交到隊列但是還沒有執行的任務,返回等待執行的任務列表,同時此方法不會等待那些正在執行的任務執行完,等待執行的任務會從線程池隊列移除。
  • isShutdown:線程池是否關閉
  • isTerminated :判斷線程池關閉後所有的任務是否都執行完了,注意這個方法只有在 shutdownshutdownNow方法調用後纔有效
  • awaitTermination:阻塞,直到一下任務情況出現:
    • shutdown調用後所有任務執行完成
    • 超時
    • 當前線程中斷

例子

下面寫一些實際例子展示上面的幾個關閉方法

public class ThreadPoolShutdownTest {
   public static void main(String[] args){
       ThreadPoolExecutor service = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
       service.submit(new MyRunnable());
       service.submit(new MyRunnable2());
       service.shutdown();
       System.out.println(service.getQueue());
       System.out.println(service.isShutdown());
       System.out.println(service.isTerminated());
   }
   static class MyRunnable implements Runnable{
       public void run() {
           try{
               System.out.println("ThreadId:"+Thread.currentThread()+",開始執行");
               //這裏模擬的是執行時間較長的任務
               TimeUnit.SECONDS.sleep(10);
               System.out.println("ThreadId:"+Thread.currentThread()+",執行完成");
           }catch (InterruptedException e){
               System.out.println("ThreadId:"+Thread.currentThread()+",中斷");
           }
       }
   }
   static class MyRunnable2 implements Runnable{
       public void run() {
           //這裏模擬的是執行時間短的任務
           System.out.println("ThreadId:"+Thread.currentThread()+",開始執行");
           System.out.println("ThreadId:"+Thread.currentThread()+",執行完成");
       }
   }
}

運行結果:

ThreadId:Thread[pool-1-thread-1,5,main],開始執行
[java.util.concurrent.FutureTask@23fc625e]
true
false
ThreadId:Thread[pool-1-thread-1,5,main],執行完成
ThreadId:Thread[pool-1-thread-1,5,main],開始執行
ThreadId:Thread[pool-1-thread-1,5,main],執行完成

在上述代碼中我們使用了只有一個線程且無界隊列,可以看到在我們調用shutdown,線程池隊列裏還有一個提交了但是等待執行的任務java.util.concurrent.FutureTask@23fc625e,我們的正在執行的任務和在等待隊列中的任務還是會繼續執行,isTerminated返回false也表明線程池在關閉的時候還有任務沒執行完,那麼這種方式的缺點是什麼呢?

  • 當線程池關閉後,無論是已經在執行的任務還是提交了但未執行的任務還是會繼續執行,如果某個任務執行時間很長或者阻塞了,這樣會導致我們的應用程序無法關閉,有的時候這可能不是我們想要的結果
    將例子中的 service.shutdown();換成
        List<Runnable> runnableList =service.shutdownNow();
        System.out.println(runnableList);

運行結果:

ThreadId:Thread[pool-1-thread-1,5,main],開始執行
ThreadId:Thread[pool-1-thread-1,5,main],中斷
[java.util.concurrent.FutureTask@23fc625e]
[]
true
true

可以看到線程池中的隊列裏沒有等待的任務了,這裏isTerminated返回true,只要調用了shutdownNow,isTerminated一定返回true
那這種存在的問題是什麼呢?可以看到在執行的任務被中斷了,這可能也不是我們想要的結果

正確關閉

  • 首先任務要能夠響應中斷
  • 關閉流程:
    • 調用shutdown禁止提交新的任務
    • 調用 awaitTermination等待任務執行完成
    • 調用shutdownNow強制關閉那些執行任務過長(可能無法正常停止)的任務
       long time_out=1*60 ;//超時時間,自己根據任務特點設置
       //第一步,調用shutdown等待在執行的任務和提交等待的任務執行,同時不允許提交任務
       service.shutdown();
       try {
           if(!service.awaitTermination(time_out, TimeUnit.SECONDS)){
               //如果等待一段時間後還有任務在執行中被中斷或者有任務提交了未執行
               //1.正在執行被中斷的任務需要編寫任務代碼的時候響應中斷
               List<Runnable> waitToExecuteTaskList = service.shutdownNow();
               //2.處理提交了未執行的任務,一般情況不會出現
               for(Runnable runnable:waitToExecuteTaskList){
                   //todo
               }
           }
       }catch (InterruptedException e){//如果被中斷了
           //1.正在執行被中斷的任務需要編寫任務代碼的時候響應中斷
           List<Runnable> waitToExecuteTaskList = service.shutdownNow();
           //2.處理提交了未執行的任務,一般情況不會出現
           for(Runnable runnable:waitToExecuteTaskList){
               //todo
           }
       }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章