Android 執行緒優化之執行緒池shutdown方法

艾陽丶發表於2017-04-20

問題: 在使用執行緒池之前,直接new Thread 建立子執行緒,如果規定時間內沒有結束,或者切換頁面等不需要子執行緒繼續執行,就會呼叫interrupted()中斷。那麼現在用了執行緒池之後想要實現類似情況該如何處理呢?呼叫shutdown()方法停止就可以咯!?  no,實際上shutdown()只是用來通知並不能實現停止或中斷。

shutdown只是起到通知的作用

我們來假設如下場景:
學校裡在課上老師出了一些問題安排全班同學進行解答並對學生說“開問題解答完畢後請舉手示意!”
如果有學生解答完畢後會舉手對老師說“老師我做完了!”,如果大家都解題完畢後上課結束。

上面的場景對應於ExecutorService裡的方法的話是下面的樣子。
老師: ExecutorService
學生: ExecutorService裡的執行緒
問題: 通過引數傳遞給ExecutorService.execute的任務(Runnable)
授課: main執行緒
學校: Java程式

 

“問題解答完畢後請舉手示意!”是shutdown方法。“老師我做完了!”是各個任務(Runnable)的執行結束。
所有的任務(Runnable)都結束了的話main執行緒(授課)也結束了。

在這裡,我們假設試卷中有難度較大的問題,當然學生解答較難的問題也會比較花時間。
在上面的場景中老師除了shutdown方法之外什麼也做不了,只能呆呆得等著學生們說,“老師我做完了!”之後才可以有下一步動作。
這都是因為shutdown方法只是用來通知的方法。

這時如果即使授課時間結束(main執行緒結束),學校也不能放學(Java程式結束),因為學生們還在解題中呢。這個時候如果你是老師你會怎麼做?

一般的情況肯定是經過一定的時間在授課快要結束的時候,如果還有人沒有解答出來的話,或者公佈給大家解題方法,

或者作為課後習題讓學生回去繼續思考,然後結束上課對不對!

 

2、定好下課時間後等待結束

 

如果經過了一定的時間任務(Runnable)還不結束的時候我們可以通過中止任務(Runnable)的執行,以防止一直等待任務的結束。
awaitTermination方法正是可以實現這個中止作用的角色。

具體的使用方法是,在shutdown方法呼叫後,接著呼叫awaitTermination方法。這時只需要等待awaitTermination方法裡第一個引數指定的時間。
如果在指定的時間內所有的任務都結束的時候,返回true,反之返回false。返回false意味著課程結束的時候還有題目沒有解答出來的學生。

通過shutdownNow方法,我們可以作為老師向同學發出“沒有解答出來的同學明天給出解答”的命令後結束授課。

shutdownNow方法的作用是向所有執行中的執行緒發出interrupted以中止執行緒的執行。這時,各個執行緒會丟擲InterruptedException異常(前提是
執行緒中執行了sleep等會丟擲異常的方法)

所以正確的中止執行緒的方法如下:

[java] view plain copy
  1. public static void main(String[] args) {  
  2.    
  3.     ExecutorService pool = Executors.newFixedThreadPool(5);  
  4.     final long waitTime = 8 * 1000;  
  5.     final long awaitTime = 5 * 1000;  
  6.    
  7.     Runnable task1 = new Runnable(){  
  8.         public void run(){  
  9.             try {  
  10.                 System.out.println("task1 start");  
  11.                 Thread.sleep(waitTime);  
  12.                 System.out.println("task1 end");  
  13.             } catch (InterruptedException e) {  
  14.                 System.out.println("task1 interrupted: " + e);  
  15.             }  
  16.         }  
  17.     };  
  18.    
  19.     Runnable task2 = new Runnable(){  
  20.         public void run(){  
  21.             try {  
  22.                 System.out.println("  task2 start");  
  23.                 Thread.sleep(1000);  
  24.                 System.out.println("  task2 end");  
  25.             } catch (InterruptedException e) {  
  26.                 System.out.println("task2 interrupted: " + e);  
  27.             }  
  28.         }  
  29.     };  
  30.     // 讓學生解答某個很難的問題  
  31.     pool.execute(task1);  
  32.    
  33.     // 生學生解答很多問題  
  34.     for(int i=0; i<1000; ++i){  
  35.         pool.execute(task2);  
  36.     }  
  37.    
  38.     try {  
  39.         // 向學生傳達“問題解答完畢後請舉手示意!”  
  40.         pool.shutdown();  
  41.    
  42.         // 向學生傳達“XX分之內解答不完的問題全部帶回去作為課後作業!”後老師等待學生答題  
  43.         // (所有的任務都結束的時候,返回TRUE)  
  44.         if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){  
  45.             // 超時的時候向執行緒池中所有的執行緒發出中斷
  46.             pool.shutdownNow();  
  47.         }  
  48.     } catch (InterruptedException e) {  
  49.         // awaitTermination方法被中斷的時候也中止執行緒池中全部的執行緒的執行。  
  50.         System.out.println("awaitTermination interrupted: " + e);  
  51.         pool.shutdownNow();  
  52.     }  
  53.    
  54.     System.out.println("end");  
  55. }  

 

可以看出上面程式中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方法的時候需要另外一些步驟。


相關文章