[本文是我對Java Concurrency In Practice 7.1的歸納和總結. ?轉載請注明作者和出處, ?如有謬誤, 歡迎在評論中指正. ]
?
在java的中斷機制中, InterruptedException異常占據重要的位置. 處理InterruptedException異常的方式有:
?
1. 不catch直接向上層拋出, 或者catch住做一些清理工作之后重拋該異常. 這樣的處理使得你的方法也成為一個可中斷的阻塞方法:
// 直接向上層拋出InterruptedException, dosomething方法也是一個可中斷的阻塞方法 private void dosomething() throws InterruptedException { Thread.sleep(1000); }??
2. 有時不能向上拋出InterruptedException異常(例如父類的相應方法沒有聲明該異常), 此時catch之后, 必須設置當前線程的中斷標記為true, 以表明當前線程發生了中斷, 以便調用棧上層進行處理:
public class InterruptedExceptionHandler implements Runnable { private Object lock = new Object(); @Override public void run() { while (!Thread.currentThread().isInterrupted()) { dosomething(); } } private void dosomething() { try { // Object.wait是一個可中斷的阻塞方法, 如果在其阻塞期間檢查到當前線程的中斷標記為true, 會重置中斷標記后從阻塞狀態返回, 并拋出InterruptedException異常 synchronized (lock) { lock.wait(); } } catch (InterruptedException e) { System.out.println("InterruptedException happened"); // catch住InterruptedException后設置當前線程的中斷標記為true, 以供調用棧上層進行相應的處理 // 在此例中, dosomething方法的調用棧上層是run方法. Thread.currentThread().interrupt(); } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new InterruptedExceptionHandler()); t.start(); // 啟動線程1s后設置其中斷標記為true Thread.sleep(1000); t.interrupt(); } }?
主線程啟動InterruptedExceptionHandler線程1s后, 設置InterruptedExceptionHandler線程的中斷標記為true. 此時InterruptedExceptionHandler線程應該阻塞在wait方法上, 由于wait方法是可中斷的阻塞方法, 所以其檢查到中斷標記為true時, 將重置當前線程的中斷標記后拋出InterruptedException, dosomething方法catch住InterruptedException異常后, 再次將當前線程的中斷標記設置為true, run方法檢查到中斷標記為true, 循環不再繼續. 假如dosomething方法catch住InterruptedException異常后沒有設置中斷標記, 其調用棧上層的run方法就無法得知線程曾經發生過中斷, 循環也就無法終止.
?
3. 還有一種情形比較特殊: 我們希望發生了InterruptedException異常后仍然繼續循環執行某阻塞方法, 此時應該將中斷狀態保存下來, 當循環完成后再根據保存下來的中斷狀態執行相應的操作:
public class InterruptedExceptionContinueHandler implements Runnable { private BlockingQueue<Integer> queue; public InterruptedExceptionContinueHandler(BlockingQueue<Integer> queue) { this.queue = queue; } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { dosomething(); } System.out.println(queue.size()); } private void dosomething() { // cancelled變量用于表明線程是否發生過中斷 boolean cancelled = false; for (int i = 0; i < 10000; i++) { try { queue.put(i); } catch (InterruptedException e) { // 就算發生了InterruptedException, 循環也希望繼續運行下去, 此時將cancelled設置為true, 以表明遍歷過程中發生了中斷 System.out.println("InterruptedException happened when i = " + i); cancelled = true; } } // 如果當前線程曾經發生過中斷, 就將其中斷標記設置為true, 以通知dosomething方法的上層調用棧 if (cancelled) { Thread.currentThread().interrupt(); } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new InterruptedExceptionContinueHandler(new LinkedBlockingQueue<Integer>())); t.start(); // 啟動線程2ms后設置其中斷標記為true Thread.sleep(2); t.interrupt(); } }
在我的機器中, 輸出結果如下:
InterruptedException happened when i = 936
size = 9999
隊列的size是9999而不是10000, 是因為i = 936時發生了InterruptedException異常, 該次put沒有成功.
為什么不在發生InterruptedException時就設置當前線程的中斷標記, 而非要繞一圈? 假設將dosomething方法改為:
private void dosomething() { for (int i = 0; i < 10000; i++) { try { queue.put(i); } catch (InterruptedException e) { System.out.println("InterruptedException happened when i = " + i); Thread.currentThread().interrupt(); } } }
運行后發現結果類似為:
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方法中, 且后續代碼不檢查中斷狀態時:
public static void main(String[] args) { // main方法已經是調用棧的最上層, 此時可以catchInterruptedException后不做任何處理 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }