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

7
我遇到了一个问题,需要处理InterruptedException,但无法将其向上传递,也不认为我的代码应该允许它被吞掉。准确地说,我正在开发一个分布式锁实现,请求后端服务可能会被中断甚至超时 - 当然,java.util.concurrent.Lock没有考虑这种情况,也不允许我抛出InterruptedException。我正在努力编写正确的非抛出lock()、tryLock()和unlock()方法的实现。
那么,问题是 - 处理这种情况的正确策略是什么?从目前的角度来看,我只看到三个选项(每个选项都有一定的风险):
1. 在lock / tryLock / unlock方法中忽略中断异常,重试/返回false/假设即使请求没有到达目的地,TTL最终会解锁记录。这显然不是最好的解决方案,因为它希望一切都能顺利进行,而不是解决问题。 2. 包装在RuntimeException子类中。这似乎也是可怕的解决方案,因为客户端代码将必须使用具体的实现而不是原始接口,并且未经检查的异常肯定不是为这样的目的而设计的。 3. 使用“强制”Thread.currentThread().interrupt()调用。我不喜欢这种方式,因为它基本上告诉线程处理自己的中断而不是传递有关调用被中断的通知;此外,据我所知,如果没有外部轮询,它将使线程最终但不是立即处理中断,可能在完全不同的位置。
(当然,还有一种选项是允许客户端代码配置所需的行为,但这仍然不能为我提供真正好的解决方案)
是否有比我描述的更好的方法?如果没有,哪个选项应优先考虑?
3个回答

7
让我讨论一下你可以选择的每个选项。
  1. 忽略中断异常
这是错误的。当你实现像库这样其他用户将依赖的东西时,永远不应该吞噬异常。在这些情况下,除非你将其作为提供更有意义信息的不同异常传播,否则吞噬异常是不明智的。InterruptedException基本上是一个请求,要求取消线程,无论锁是否稍后解锁,都不应该从客户端中抑制此信息。客户端需要知道某人想要停止此线程正在执行的工作单元。
  1. 包装在RuntimeException中
不行。出于完全相同的原因,这也是错误的。传播InterruptedException的原因是让客户端知道已经发出了取消执行线程的请求,因此将其包装在RuntimeException中是错误的,因为这会导致丢失此信息。
  1. 使用/强制Thread.currentThread().interrupt()调用
根据用例,这可能是对的或错误的。问问自己是否可以传播InterruptedException。
  • 如果可以这样做(在您的情况下不行),那么您可以声明您的方法会抛出InterruptedException,让调用方负责处理。这通常是当您调用一个方法(比如operation())时,该方法会抛出InterruptedException,并且除非该调用完成,否则您将无法继续进行。假设operation()抛出InterruptedException,那么您除了传播此异常之外别无他法。因此,您不应该捕获异常。在这种情况下,只需声明您的方法抛出InterruptedException,然后就完成了。

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


3
对我来说,答案几乎总是 3。
Java 使用一种协作中断模型:对我来说,它感觉像是非常英式的方法:
“我说老兄,请问您介意停止正在做的事情吗?如果不太麻烦的话。”
但是没有必要及时地(或者根本不)采取中断。用一句 罗宾·威廉姆斯 的名言来说明:
“停止!……否则……我会再说一遍停止!”
您可以编写代码定期检查中断,也可以不检查——如果您在每个地方都这样做,它会变得非常混乱和重复。但是,如果您不想在被中断时执行任何操作,至少应保留发生中断的事实,以便调用代码可以相应地执行操作。
关于InterruptedException,实际上没有什么特别的地方 - 它只是Exception的一个空子类。通常情况下,它只有在特定方法检查Thread.interrupted()或.isInterrupted()时才会被抛出。因此,调用interrupt()并不会立即导致线程停止正在执行的操作,这就是协作式中断的本质。
为了说明我为什么在上面说“几乎总是”:Java教程这样描述中断:

中断是对线程的指示,告诉它应该停止正在做的事情并做其他事情。由程序员决定线程如何响应中断,但很常见的是线程终止。

除非特殊情况,我很少会想要中断线程执行其他任务。

如果我想让线程“做其他事情”,我可能会使用执行器服务:每个“事情”都由一个单独的Runnable表示,所以我甚至不知道它们是否在同一个线程中完成。

因此,我通常会中断线程,然后抛出RuntimeException

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

每个 Runnable 在被中断时只会完成它正在做的事情;执行器服务决定是否执行另一个任务。
基本上只有在编写框架级代码(如 ExecutorService)时,我才会选择在中断后继续执行。

相比之下,美国的做法可能涉及手枪 :-) - Stephen C
@StephenC 如果线程没有停止,显然问题是使用的枪不够多。 - Andy Turner

1

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接