Java正確處理InterruptedException的方法(java併發編程第7章)

要想討論正確處理InterrupedtException的方法,就要知道InterruptedException是什麼。

根據Java Doc的定義

Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity. Occasionally a method may wish to test whether the current thread has been interrupted, and if so, to immediately throw this exception.

意思是說當一個線程處於等待,睡眠,或者佔用,也就是說阻塞狀態,而這時線程被中斷就會拋出這類錯誤,但是線程並沒有被中斷,任何時候,出現 該異常都需要手動在代碼中再次中斷該異常。Java6之後結束某個線程A的方法是A.interrupt()。如果這個線程正處於非阻塞狀態,比如說線程正在執行某些代碼的時候,不過被interrupt,那麼該線程的interrupt變量會被置爲true,告訴別人說這個線程被中斷了(只是一個標誌位,這個變量本身並不影響線程的中斷與否),而且線程會被中斷,這時不會有interruptedException。但如果這時線程被阻塞了,比如說正在睡眠,那麼就會拋出這個錯誤。請注意,這個時候變量interrupt沒有被置爲true,而且也沒有人來中斷這個線程。這類阻塞操作中中斷線程,需要做如下操作。


在java的中斷機制中, InterruptedException異常佔據重要的位置. 處理InterruptedException異常的方式有:

 

1. 不catch直接向上層拋出, 或者catch住做一些清理工作之後重拋該異常. 這樣的處理使得你的方法也成爲一個可中斷的阻塞方法:

Java代碼  收藏代碼
  1. // 直接向上層拋出InterruptedException, dosomething方法也是一個可中斷的阻塞方法  
  2. private void dosomething() throws InterruptedException {  
  3.     Thread.sleep(1000);  
  4. }  
  

2. 有時不能向上拋出InterruptedException異常(例如父類的相應方法沒有聲明該異常), 此時catch之後, 必須設置當前線程的中斷標記爲true, 以表明當前線程發生了中斷, 以便調用棧上層進行處理:

Java代碼  收藏代碼
  1. public class InterruptedExceptionHandler implements Runnable {  
  2.     private Object lock = new Object();  
  3.   
  4.     @Override  
  5.     public void run() {  
  6.         while (!Thread.currentThread().isInterrupted()) {  
  7.             dosomething();  
  8.         }  
  9.     }  
  10.   
  11.     private void dosomething() {  
  12.         try {  
  13.             // Object.wait是一個可中斷的阻塞方法, 如果在其阻塞期間檢查到當前線程的中斷標記爲true, 會重置中斷標記後從阻塞狀態返回, 並拋出InterruptedException異常  
  14.             synchronized (lock) {  
  15.                 lock.wait();  
  16.             }  
  17.         } catch (InterruptedException e) {  
  18.             System.out.println("InterruptedException happened");  
  19.             // catch住InterruptedException後設置當前線程的中斷標記爲true, 以供調用棧上層進行相應的處理  
  20.             // 在此例中, dosomething方法的調用棧上層是run方法.  
  21.             Thread.currentThread().interrupt();  
  22.         }  
  23.     }  
  24.       
  25.     public static void main(String[] args) throws InterruptedException {  
  26.         Thread t = new Thread(new InterruptedExceptionHandler());  
  27.         t.start();  
  28.         // 啓動線程1s後設置其中斷標記爲true  
  29.         Thread.sleep(1000);  
  30.         t.interrupt();  
  31.     }  
  32. }   

主線程啓動InterruptedExceptionHandler線程1s後, 設置InterruptedExceptionHandler線程的中斷標記爲true. 此時InterruptedExceptionHandler線程應該阻塞在wait方法上, 由於wait方法是可中斷的阻塞方法, 所以其檢查到中斷標記爲true時, 將重置當前線程的中斷標記後拋出InterruptedException, dosomething方法catch住InterruptedException異常後, 再次將當前線程的中斷標記設置爲true, run方法檢查到中斷標記爲true, 循環不再繼續. 假如dosomething方法catch住InterruptedException異常後沒有設置中斷標記, 其調用棧上層的run方法就無法得知線程曾經發生過中斷, 循環也就無法終止.

 

3. 還有一種情形比較特殊: 我們希望發生了InterruptedException異常後仍然繼續循環執行某阻塞方法, 此時應該將中斷狀態保存下來, 當循環完成後再根據保存下來的中斷狀態執行相應的操作:

Java代碼  收藏代碼
  1. public class InterruptedExceptionContinueHandler implements Runnable {  
  2.     private BlockingQueue<Integer> queue;  
  3.   
  4.     public InterruptedExceptionContinueHandler(BlockingQueue<Integer> queue) {  
  5.         this.queue = queue;  
  6.     }  
  7.   
  8.     @Override  
  9.     public void run() {  
  10.         while (!Thread.currentThread().isInterrupted()) {  
  11.             dosomething();  
  12.         }  
  13.         System.out.println(queue.size());  
  14.     }  
  15.   
  16.     private void dosomething() {  
  17.         // cancelled變量用於表明線程是否發生過中斷  
  18.         boolean cancelled = false;  
  19.         for (int i = 0; i < 10000; i++) {  
  20.             try {  
  21.                 queue.put(i);  
  22.             } catch (InterruptedException e) {  
  23.                 // 就算髮生了InterruptedException, 循環也希望繼續運行下去, 此時將cancelled設置爲true, 以表明遍歷過程中發生了中斷  
  24.                 System.out.println("InterruptedException happened when i = " + i);  
  25.                 cancelled = true;  
  26.             }  
  27.         }  
  28.         // 如果當前線程曾經發生過中斷, 就將其中斷標記設置爲true, 以通知dosomething方法的上層調用棧  
  29.         if (cancelled) {  
  30.             Thread.currentThread().interrupt();  
  31.         }  
  32.     }  
  33.       
  34.     public static void main(String[] args) throws InterruptedException {  
  35.         Thread t = new Thread(new InterruptedExceptionContinueHandler(new LinkedBlockingQueue<Integer>()));  
  36.         t.start();  
  37.           
  38.         // 啓動線程2ms後設置其中斷標記爲true  
  39.         Thread.sleep(2);  
  40.         t.interrupt();  
  41.     }  
  42. }  

在我的機器中, 輸出結果如下:

InterruptedException happened when i = 936

size = 9999

隊列的size是9999而不是10000, 是因爲i = 936時發生了InterruptedException異常, 該次put沒有成功.

爲什麼不在發生InterruptedException時就設置當前線程的中斷標記, 而非要繞一圈? 假設將dosomething方法改爲:

Java代碼  收藏代碼
  1. private void dosomething() {  
  2.     for (int i = 0; i < 10000; i++) {  
  3.         try {  
  4.             queue.put(i);  
  5.         } catch (InterruptedException e) {  
  6.             System.out.println("InterruptedException happened when i = " + i);  
  7.             Thread.currentThread().interrupt();  
  8.         }  
  9.     }  
  10. }  

運行後發現結果類似爲:

InterruptedException happened when i = 936

InterruptedException happened when i = 937

...

InterruptedException happened when i = 9998

InterruptedException happened when i = 9999

size = 936

catch住InterruptedException後立即將當前線程的中斷標記設置爲true, 就會導致put方法又拋出InterruptedException異常, 如此往復直到循環結束.

 

4. 最不可取的是catch了InterruptedException異常但是不做任何處理, 這樣一來調用棧上層就無法得知當前線程是否發生過中斷. 只有一種情況下可以這樣處理: 當InterruptedException發生在調用棧的最上層, 如run方法, 或者main方法中, 且後續代碼不檢查中斷狀態時:

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.     // main方法已經是調用棧的最上層, 此時可以catchInterruptedException後不做任何處理  
  3.     try {  
  4.         Thread.sleep(2);  
  5.     } catch (InterruptedException e) {  
  6.         e.printStackTrace();  
  7.     }  
  8. }  


發佈了111 篇原創文章 · 獲贊 62 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章