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