遇見InterruptedException異常,怎么辦?


前言

在Java語言的開發工作中,我們經常會碰到這樣一類異常--InterruptedException(中斷異常)。在絕大多數時候,我們的處理方式無非是catch注它,然后再輸出異常信息,更或者是干脆直接忽略它了。那么這是否是一種正確的處理方式呢,要想搞清楚這件事,我們又必須要了解什么是InterruptedException,什么情況下會導致此異常的發生呢?本文筆者來簡單講述講述這方面的內容,了解中斷中斷異常方面的知識將有助於我們在分布式的程序中處理這樣的異常。


什么是中斷異常?

現在一個首要的問題來了,什么是中斷異常,InterruptedException到底意味着什么意思呢?下面是筆者通過閱讀IBM官網上面對於此的定義:


When a method throws InterruptedException, it is telling you several things in addition to the fact that it can throw a particular checked exception. It is telling you that it is a blocking method and that it will make an attempt to unblock and return early


大致意思如下:InterruptedException實質上是一個檢測異常,它表明又一個阻塞的被中斷了,它嘗試進行解除阻塞操作並返回地更早一些。中斷阻塞方法的操作線程並不是自身線程干的,而是其它線程。而中斷操作發生之后,隨后會拋出一個InterruptedException,伴隨着這個異常拋出的同時,當前線程的中斷狀態重新被置為false。這時,我們談到了線程的中斷狀態,可能有些讀者會有點暈了,下面我們來理理這段關系。


1.public static boolean interrupted(); // 檢測當前線程是否已經中斷,此方法會清除中斷狀態,也就是說,假設當前線程中斷狀態為true,第一次調此方法,將返回true,表明的確已經中斷了,但是第二次調用后,將會返回true,因為第一次調用的操作已將中斷狀態重新置為false了。

2.public boolean isInterrupted() ; // 檢測當前線程是否已經中斷,此方法與上一方法的區別在於此方法不會清除中斷狀態。

3.public void interrupt(); //將線程中斷狀態設置為true,表明此線程目前是中斷狀態。此時如果調用isInterrupted方法,將會得到true的結果。


通過上述方法的解釋,我們可以得出這樣的一個結論:interrupt方法本質上不會進行線程的終止操作的,它不過是改變了線程的中斷狀態。而改變了此狀態帶來的影響是,部分可中斷的線程方法(比如Object.wait, Thread.sleep)會定期執行isInterrupted方法,檢測到此變化,隨后會停止阻塞並拋出InterruptedException異常。但這是否意味着隨后線程的退出呢?不是的,

下面是筆者對於此3個方法進行的一個簡單測試demo。代碼如下:

public class InterruptedException {
    public static void main(String[] args) throws Exception {
        System.out.println("初始中斷狀態:" + Thread.currentThread().isInterrupted());
        Thread.currentThread().interrupt();
        System.out.println("執行完interrupt方法后,中斷狀態:" + Thread.currentThread().isInterrupted());


        System.out.println("首次調用interrupted方法返回結果:" + Thread.currentThread().interrupted());
        System.out.println("此時中斷狀態:" + Thread.currentThread().isInterrupted());
        System.out.println("第二次調用interrupted方法返回結果:" + Thread.currentThread().interrupted());
        System.out.println("此時中斷狀態:" + Thread.currentThread().isInterrupted());
    }
}
輸出結果如下:
初始中斷狀態:false
執行完interrupt方法后,中斷狀態:true
首次調用interrupted方法返回結果:true
此時中斷狀態:false
第二次調用interrupted方法返回結果:false
此時中斷狀態:false
InterruptedException異常的拋出並不是意味着線程必須得終止,它只是提醒當前線程有中斷操作發生了,接下來怎么處理完全取決於線程本身,一般有3種處理方式:
1.“吞並”異常,當做什么事都沒發生過。
2.繼續往外拋出異常。
3.其它方式處理異常。其它處理異常的方式就有很多種了,停止當前線程或者輸出異常信息等等。
第三點其它中斷異常處理的部分,將會是本文所要重點闡述的。這里面可有不少的講究。

中斷異常的處理

此部分的內容源自於筆者最近讀過的一篇文章,IBM官網上關於JAVA理論與實踐的一篇博文。里面包含了許多種情況下的處理方式
首先我們來看第一種處理方式,可以說是最簡單的一種,直接往外拋出InterruptedException,代碼如下:

public class TaskQueue {
 private static final int MAX_TASKS = 1000;
 private BlockingQueue<Task> queue
 = new LinkedBlockingQueue<Task>(MAX_TASKS);
 public void putTask(Task r) throws InterruptedException {
 queue.put(r);
 }
 public Task getTask() throws InterruptedException {
 return queue.take();
 }
}

但是很多情況下我們會主動捕獲住這個異常,然后做進一步處理,比如說再throw它一次,如下代碼所示:

public class PlayerMatcher {
 private PlayerSource players;
 public PlayerMatcher(PlayerSource players) {
 this.players = players;
 }
 public void matchPlayers() throws InterruptedException {
 Player playerOne, playerTwo;
 try {
 while (true) {
 playerOne = playerTwo = null;
 // Wait for two players to arrive and start a new game
 playerOne = players.waitForPlayer(); // could throw IE
 playerTwo = players.waitForPlayer(); // could throw IE
 startNewGame(playerOne, playerTwo);
 }
 }
 catch (InterruptedException e) {
 // Just propagate the exception
throw e;
 }
 }
}

上面程序的意思是阻塞地等待2個運動員,最后再開始新的比賽,在等待運動員1和運動員2的過程中是有可能發生中斷的,在這里我們的做法是進行一次簡單的rethrow動作。其實這里會有2個問題,第一個如果方法在等待第二個運動員時發生中斷異常了,如果就這么重拋,會導致數據的丟失,這里的丟失指的是運動員1的請求數據,因為在此處的運行過程中,運動員1的請求操作已經執行完成。這樣的話,再下次重跑此任務的時候,還是會等待2次操作。所以,優化過后的代碼如下:

public class PlayerMatcher {
 ...
 public void matchPlayers() throws InterruptedException {
 Player playerOne, playerTwo;
 try {
 while (true) {
 playerOne = playerTwo = null;
 // Wait for two players to arrive and start a new game
 playerOne = players.waitForPlayer(); // could throw IE
 playerTwo = players.waitForPlayer(); // could throw IE
 startNewGame(playerOne, playerTwo);
 }
 }
 catch (InterruptedException e) {
 // If we got one player and were interrupted, put that player back
 if (playerOne != null)
 players.addFirst(playerOne);
 // Then propagate the exception
throw e;
 }
 }
}


還有一個問題是中斷狀態的問題,當InterruptedException拋出的時候,線程的中斷狀態將會被清除,重新置為false。此時最好的一種做法是將線程狀態重新置為true,這樣才能最完全的保留線程當前的執行狀況,而調用的方法就是前面篇幅介紹的interrupt方法。下面是一個示例代碼;

public class TaskRunner implements Runnable {
 private BlockingQueue<Task> queue;
 public TaskRunner(BlockingQueue<Task> queue) {
 this.queue = queue;
 }
 public void run() {
 try {
 while (true) {
 Task task = queue.take(10, TimeUnit.SECONDS);
 task.execute();
 }
 }
 catch (InterruptedException e) {
 // Restore the interrupted status
Thread.currentThread().interrupt();
 }
 }
}

但是有的時候我們並不總是一遇到InterruptedException,就重置狀態,一種好的做法是在程序最后執行完的時候,調用一次interrupt方法即可,捕獲異常時只需要做個標記即可,如下代碼。

public Task getNextTask(BlockingQueue<Task> queue) {
 boolean interrupted = false;
 try {
 while (true) {
 try {
 return queue.take();
 } catch (InterruptedException e) {
 interrupted = true;
 // fall through and retry
 }
 }
 } finally {
 if (interrupted)
 Thread.currentThread().interrupt();
 }
}

中斷狀態被置為true了之后,當前線程會感知到此變化,對於后續的操作行為,完全由線程本身決定。這里會牽涉到Thread.currentThread().isInterrupted()的配合調用了。
與此做法完全相反的是,我們不能夠隨隨便便把中斷異常給“吞”了,比如說下面這種方式的代碼:

public class TaskRunner implements Runnable {
 private BlockingQueue<Task> queue;
 public TaskRunner(BlockingQueue<Task> queue) {
 this.queue = queue;
 }
 public void run() {
 try {
 while (true) {
 Task task = queue.take(10, TimeUnit.SECONDS);
 task.execute();
 }
 }
 catch (InterruptedException swallowed) {
 /* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */
 }
 }
}

那什么時候我們可以毫無顧慮地吞並這異常呢,這種情況也是有的,比如說當我們明確知道一個程序將要結束退出的時候。當然了,可能還有其它一些特殊場景,這個可以根據具體應用場景來定。
綜述所述,線程中斷異常的處理絕對不能簡簡單單地處理,處理不當會丟失掉許多重要的信息,不利於我們排除問題的原因。

參考資料

[1].Dealing with InterruptedException: You caught it, now what are you going to do with it? https://www.ibm.com/developerworks/library/j-jtp05236/j-jtp05236-pdf.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM