處理 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.
}