寫在前面
InterruptedException異常可能沒你想的那麼簡單!
前言
當我們在呼叫Java物件的wait()方法或者執行緒的sleep()方法時,需要捕獲並處理InterruptedException異常。如果我們對InterruptedException異常處理不當,則會發生我們意想不到的後果!
程式案例
例如,下面的程式程式碼,InterruptedTask類實現了Runnable介面,在run()方法中,獲取當前執行緒的控制程式碼,並在while(true)迴圈中,透過isInterrupted()方法來檢測當前執行緒是否被中斷,如果當前執行緒被中斷就退出while(true)迴圈,同時,在while(true)迴圈中,還有一行Thread.sleep(100)程式碼,並捕獲了InterruptedException異常。整個程式碼如下所示。
package io.binghe.concurrent.lab08;
/**
* @author binghe
* @version 1.0.0
* @description 執行緒測試中斷
*/
public class InterruptedTask implements Runnable{
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true){
if(currentThread.isInterrupted()){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述程式碼的本意是透過isInterrupted()方法檢查執行緒是否被中斷了,如果中斷了就退出while迴圈。其他執行緒透過呼叫執行執行緒的interrupt()方法來中斷執行執行緒,此時會設定執行執行緒的中斷標誌位,從而使currentThread.isInterrupted()返回true,這樣就能夠退出while迴圈。
這看上去沒啥問題啊!但真的是這樣嗎?我們建立一個InterruptedTest類用於測試,程式碼如下所示。
package io.binghe.concurrent.lab08;
/**
* @author binghe
* @version 1.0.0
* @description 測試執行緒中斷
*/
public class InterruptedTest {
public static void main(String[] args){
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
interruptedThread.interrupt();
}
}
我們執行main方法,如下所示。
這竟然跟我們想象的不一樣!不一樣!不一樣!這是為什麼呢?
問題分析
上述程式碼明明呼叫了執行緒的interrupt()方法來中斷執行緒,但是卻並沒有起到啥作用。原因是執行緒的run()方法在執行的時候,大部分時間都是阻塞在sleep(100)上,當其他執行緒透過呼叫執行執行緒的interrupt()方法來中斷執行執行緒時,大機率的會觸發InterruptedException異常,在觸發InterruptedException異常的同時,JVM會同時把執行緒的中斷標誌位清除,所以,這個時候在run()方法中判斷的currentThread.isInterrupted()會返回false,也就不會退出當前while迴圈了。
既然問題分析清除了,那如何中斷執行緒並退出程式呢?
問題解決
正確的處理方式應該是在InterruptedTask類中的run()方法中的while(true)迴圈中捕獲異常之後重新設定中斷標誌位,所以,正確的InterruptedTask類的程式碼如下所示。
package io.binghe.concurrent.lab08;
/**
* @author binghe
* @version 1.0.0
* @description 中斷執行緒測試
*/
public class InterruptedTask implements Runnable{
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true){
if(currentThread.isInterrupted()){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
currentThread.interrupt();
}
}
}
}
可以看到,我們在捕獲InterruptedException異常的catch程式碼塊中新增了一行程式碼。
currentThread.interrupt();
這就使得我們捕獲到InterruptedException異常後,能夠重新設定執行緒的中斷標誌位,從而中斷當前執行的執行緒。
我們再次執行InterruptedTest類的main方法,如下所示。
總結
處理InterruptedException異常時要小心,如果在呼叫執行執行緒的interrupt()方法中斷執行執行緒時,丟擲了InterruptedException異常,則在觸發InterruptedException異常的同時,JVM會同時把執行執行緒的中斷標誌位清除,此時呼叫執行執行緒的isInterrupted()方法時,會返回false。此時,正確的處理方式是在執行執行緒的run()方法中捕獲到InterruptedException異常,並重新設定中斷標誌位(也就是在捕獲InterruptedException異常的catch程式碼塊中,重新呼叫當前執行緒的interrupt()方法)。
寫在最後
如果覺得文章對你有點幫助,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發程式設計技術。
最後,附上併發程式設計需要掌握的核心技能知識圖,祝大家在學習併發程式設計時,少走彎路。