(點選上方公眾號,可快速關註)
ImportNew – paddx
Java 中的受檢查異常 InterruptedException 如何處理是令人頭痛的問題,下麵是我對處理這個問題的理解。
Java 中的 InterruptedException 一直是一個令人頭疼的問題,對初級開發者來說尤其如此。但實際上不應如此,這其實是一個很容易理解的問題。我會盡可能簡單地描述這個問題。
我們從這段程式碼開始:
while (true) {
// Nothing
}
它做了什麼?什麼都沒做,只是無止境的消耗 CPU。我們能終止它嗎?在 Java 中是不行的。只有當你按下 Ctrl-C 來終止整個 JVM 時這段程式才會停止。在 Java 中沒有方式來終止一個執行緒,除非該執行緒自動退出。請務必牢記的這一原則,其它東西就顯而易見了。
我們將這個死迴圈放在一個執行緒裡:
Thread loop = new Thread(
new Runnable() {
@Override
public void run() {
while (true) {
}
}
}
);
loop.start();
// Now how do we stop it?
所以,怎樣才能停止一個需要停止的執行緒?
下麵是 Java 中設計終止一個執行緒的方法。在執行緒的外部,設定一個標識變數(flag),然後在執行緒內部檢查改標識變數,從而實現執行緒的終止。過程如下:
Thread loop = new Thread(
new Runnable() {
@Override
public void run() {
while (true) {
if (Thread.interrupted()) {
break;
}
// Continue to do nothing
}
}
}
);
loop.start();
loop.interrupt();
這是終止執行緒的唯一方式,在這個例子裡使用了兩個方法。當呼叫 loop.interrupt() ,執行緒內部將標誌位設定為 true。當呼叫 interrupted() 時,立即傳回,並將標識變數設定為 false。確實,這個方法就是這樣設計的。檢查標識變數、傳回、設定為 false。我知道這很醜陋。
因此,我從來沒有在執行緒內呼叫 Thread.interrupted() 方法,因此標識變數為 true 時執行緒不會退出,沒有人能停止這個執行緒。準確地說,我會忽略他們對 interrupt() 方法的呼叫。雖然它們會要求終止執行緒,但是我會忽略它們。它們不能讓執行緒中斷。
因此,總結一下我們現在理解的內容,一種合理的設計是透過檢查標識變數來優雅地終止執行緒。如果程式碼中不檢測標識變數,也不呼叫 Thread.interrupted(),那麼終止執行緒的方式就只能按下 Ctrl-C 了。
現在你聽明白這個邏輯了嗎?我希望是。
現在,JDK 中有一些方法來檢測標識變數,如果設定該標識變數,則會丟擲 InterruptedException。例如,Thread.sleep() 方法的設計(一種最基本的方法):
public static void sleep(long millis)
throws InterruptedException {
while (/* You still need to wait */) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// Keep waiting
}
}
為什麼要這麼做?為什麼不能等待並且不用去檢查標識變數?我相信一定有一個非常好的理由。理由如下(如果我說錯了,請修正我的錯誤):為了讓程式碼變快或是中斷準備,沒有其他理由。
如果你的程式碼足夠快,你從來不會檢測中斷標識變數,因為你不想處理任何中斷。如果你程式碼很慢,可能需要執行數秒,這時你就有可能需要處理中斷了。
這就是為什麼 InterruptedException 是受檢查異常。這種設計告訴你,如果你想在幾毫秒內停止執行緒,確定你已經做好中斷準備。實踐中一般做如下處理:
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
// Stop immediately and go home
}
現在,你可以將它拋給負責捕獲該異常的上級程式去處理。這種觀點是有人在使用執行緒,並且會捕獲該異常。理想情況下,會終止執行緒,因為這就是標識變數的功能。如果丟擲 InterruptedException,就意味著有人在檢查標識變數,執行緒需要盡可能快地終止。
執行緒的擁有者不想再等待執行緒執行,我們應該尊重擁有者的決定。
因此,當捕獲到 InterruptedException 時,你應該完成相關的操作再退出執行緒。
現在,我們再看一下 Thread.sleep() 的程式碼:
public static void sleep(long millis)
throws InterruptedException {
while (/* … */) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
}
請記住,Thread.interrupted() 不僅僅是傳回標識變數的值,而且會將標識變數的值設定為 false。因此,一旦丟擲 InterruptedException 異常,標誌變數將會重置。執行緒不再收到任何擁有者傳送的中斷請求。
執行緒的所有者要求停止執行緒,Thread.sleep() 監測到該請求並將其刪除,再丟擲 InterruptedException。如果你再次呼叫 Thread.sleep(),就不會響應任何中斷請求,也不會丟擲任何異常。
知道我想要說的是什麼嗎?不要丟失 InterruptedException,這一點非常重要。我們不能吞噬該異常並繼續執行。這嚴重違背了 Java 多執行緒原則。所有者(執行緒的所有者)要求停止執行緒,而我們卻將其忽略,這是非常不好的想法。
下麵是大多數人對 InterruptedException 的處理:
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
這看起來是符合邏輯的,但是這不能保證上層程式真正停止並退出。上層可能捕獲了執行時異常,所以這個執行緒還是存活的。執行緒所有者將會非常失望。
我們必須通知上層捕獲了一個中斷請求。我們不能只丟擲執行時異常,這種行為太不負責了。當一個執行緒接收一個中斷請求時,我們不能只是將其轉換成為一個 RuntimeException。我們不能將這種嚴峻的情況如此輕鬆地對待。
這是我們應該做的:
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt(); // Here!
throw new RuntimeException(ex);
}
我們需要將標識變數重新設定為 true。
現在,沒有人會譴責我們以不負責的態度來處理標識變數。我們發現其狀態為 true,將其清理,重新設定為 true,最後丟擲執行時異常。接下來會發生什麼?我們已經不關心了。
這就是我認為的處理方式。你可以找到這個問題更詳細的官方描述:Java 理論與實踐:InterruptedException 處理。
http://www.ibm.com/developerworks/library/j-jtp05236/
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能