如何正确处理可能未传递给客户端代码的 InterruptedException?

How does one correctly handle InterruptedException that may not be passed to client code?

我偶然发现了一种情况,我必须处理 InterruptedException,但无法向上传递并且不觉得我的代码应该被允许吞下它。准确地说,我正在研究分布式锁实现,对支持服务的请求可能会中断甚至超时——当然,java.util.concurrent.Lock 不考虑这种情况,也不允许我吐出 InterruptedException。我正在努力为非抛出 lock()tryLock()unlock() 方法编写正确的实现。

所以,问题是 - 处理这种情况的正确策略是什么?从目前的角度来看,我只看到三个选项(我觉得每个选项都有味道):

  1. 忽略lock/tryLock/unlock方法中的中断异常,重试/返回false/假设即使请求没有到达目的地,TTL最终也会解锁记录。这显然不是最好的解决方案,因为它希望一切都好,而不是处理问题。
  2. 包装 RuntimeException 继承人。这似乎也是一个糟糕的解决方案,因为客户端代码必须使用具体实现而不是原始接口,并且未经检查的异常当然不是为了那样的目的。
  3. 使用 force Thread.currentThread().interrupt() 调用。我不喜欢这种方式,因为它基本上告诉线程处理它自己的中断,而不是传递有关调用被中断的通知;另外,据我所知,如果没有外部轮询,它最终会产生线程,但不会立即处理中断,可能在完全不同的地方。

(当然,有一个选项允许客户端代码配置所需的行为,但这仍然没有为我提供一个真正好的解决方案)

有没有比我描述的更好的方法?如果不是,应该优先选择哪一个?

这完全取决于您的应用程序,特别是线程中 InterruptedException 的含义。

选项 1 对我来说是一个不好的做法:依赖其他机制会使您的代码不清晰和可怕。你总是可以捕获异常并只使用finally方法释放所有锁定的资源。

然后重新启动异常或隐藏它(可能返回特定结果)取决于您和该异常在您的应用程序中的具体含义。

让我讨论一下您的每一个可用选项。

  1. Ignore interrupted exception

这是错误的。当您实现其他用户将依赖的库之类的东西时,吞下异常永远是不对的。在这些情况下,吞下一个异常永远不会是明智的,除非你将它作为一个不同的异常传播,为客户端提供更有意义的信息。 InterruptedException 基本上是取消您的线程的请求,并且无论以后是否解锁锁,都不应从客户端抑制此信息。客户端需要知道有人希望停止此线程正在执行的工作单元。

  1. Wrap in RuntimeException

没有。由于与上述完全相同的原因,这也是错误的。传播 InterruptedException 的原因是让客户端知道已发出取消正在执行的线程的请求,因此将其包装在 RuntimeException 中是错误的,因为此信息已丢失。

  1. Use/force Thread.currentThread().interrupt() call

这可能是正确的,也可能是错误的,具体取决于用例。问问自己是否可以传播 InterruptedException。

  • 如果可以这样做(不是你的情况,但是),那么你可以声明你的方法抛出 InterruptedException 并让上面的调用者担心需要什么完成。当您调用抛出 InterruptedException 的方法(比如 operation())时,通常会出现这种情况,除非此调用完成,否则您将无法继续进行。假设 operation() 抛出 InterruptedException 那么除了传播此异常之外您无能为力。所以你不应该抓住这个例外。在这种情况下,只需声明您的方法抛出 InterruptedException 即可完成

  • 如果不能这样做,那么正确的处理方法是强制调用 interrupt()。使用它可以抑制异常,但您仍然可以让客户端选择检查标志以查看是否发出了中断请求。你是对的。这需要客户端轮询而不是处理中断。但这并没有错。如果您不希望客户端进行轮询,那么传播异常将是更好的选择。但这并不总是可能的,您的示例就是这样一个用例。在许多情况下,执行线程即使在被中断时也可以 return 一些有意义的信息。所以在这种情况下,异常被抑制了,但是仍然可以通过调用 interrupt() 方法在上面传递存在终止请求的信息。因此,客户端可以只使用 return 从部分计算中得到的结果,也可以根据用例轮询来检查是否设置了中断标志。因此,您通过这样做为客户提供了更大的灵活性。

对我来说,答案几乎总是 3。

Java使用合作中断模式:对我来说感觉很英式:

I say, old chap, would you mind stopping what you are doing, if it is not too much trouble?

但是及时(或者,事实上,根本)没有对中断采取行动的内疚。要使用 Robin Williams quote:

Stop! ...or... I'll say stop again!

您可以编写代码来定期检查是否有中断 - 如果您到处都这样做,它会变得非常混乱和重复。但是,如果你不想在被打扰时做任何事情,你至少应该保留中断 did 发生的事实,以便调用 的代码 是否想做一些相应的事情。

InterruptedException 并没有什么特别之处——它实际上是 Exception 的一个空子类。如果特定方法检查 Thread.interrupted().isInterrupted(),通常只会首先抛出它。所以,我不会担心调用 interrupt() 不会立即导致线程停止它正在做的事情——这就是合作中断的本质。​​


为了证明我在上面说 "almost always" 的原因:Java tutorial 如此描述中断:

An interrupt is an indication to a thread that it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.

除了想要终止中断的线程之外,我很少做任何事情。

如果我想要一个线程 "to do something else",我可能会使用执行程序服务:每个要完成的 "things" 都由一个单独的 Runnable 表示,所以我甚至不知道它们是否在同一个线程中完成。

所以我通常会中断线程,然后抛出 RuntimeException:

catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new RuntimeException(e);
}

每个Runnable在被打扰时只是完成它正在做的事情;执行者服务决定是否执行另一项任务。

基本上只有写框架级别的代码(比如ExecutorService)我才会选择中断后继续