處理 InterruptedException
InterruptedException
是一個令人困惑的野獸 - 它出現在看似無害的方法,如 Thread.sleep()
,但處理它錯誤導致難以管理的程式碼在併發環境中表現不佳。
在最基本的情況下,如果發現了一個 InterruptedException
,那就意味著有人在某個地方,在你的程式碼當前執行的執行緒上呼叫 Thread.interrupt()
。你可能傾向於說“這是我的程式碼!我永遠不會打斷它!” 因此做這樣的事情:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
但這恰恰是處理不可能事件發生的錯誤方法。如果你知道你的申請永遠不會遇到任何問題,你應該將此類事件視為嚴重違反你的計劃假設並儘快退出。
處理不可能中斷的正確方法是這樣的:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
這做了兩件事; 它首先恢復執行緒的中斷狀態(好像首先沒有丟擲 InterruptedException
),然後它丟擲一個 AssertionError
,表明你的應用程式的基本不變數已被違反。如果你肯定知道你永遠不會打斷執行緒,那麼這個程式碼執行是安全的,因為永遠不會到達 catch
塊。
使用 Guava 的 Uninterruptibles
類有助於簡化這種模式; 呼叫 Uninterruptibles.sleepUninterruptibly()
忽略執行緒的中斷狀態,直到睡眠持續時間到期(此時它被恢復以便以後呼叫以檢查並丟擲自己的 InterruptedException
)。如果你知道你永遠不會中斷這樣的程式碼,那麼安全地避免需要在 try-catch 塊中包裝你的 sleep 呼叫。
但是,更常見的是,你不能保證你的執行緒永遠不會被中斷。特別是如果你正在編寫將由 Executor
或其他一些執行緒管理執行的程式碼,那麼你的程式碼必須立即響應中斷,否則你的應用程式將停止甚至死鎖。
在這種情況下,最好的辦法通常是讓 InterruptedException
向上傳播呼叫堆疊,依次為每個方法新增一個 throws InterruptedException
。這可能看起來像 kludgy 但它實際上是一個理想的屬性 - 你的方法的簽名現在向呼叫者表明它將迅速響應中斷。
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
在有限的情況下(例如,在覆蓋任何未檢查任何已檢查異常的方法時),你可以重置中斷狀態而不會引發異常,期望接下來執行的任何程式碼都可以處理中斷。這延遲了處理中斷但不完全抑制它。
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}