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