如何正确处理可能未传递给客户端代码的 InterruptedException?
How does one correctly handle InterruptedException that may not be passed to client code?
我偶然发现了一种情况,我必须处理 InterruptedException
,但无法向上传递并且不觉得我的代码应该被允许吞下它。准确地说,我正在研究分布式锁实现,对支持服务的请求可能会中断甚至超时——当然,java.util.concurrent.Lock
不考虑这种情况,也不允许我吐出 InterruptedException。我正在努力为非抛出 lock()
、tryLock()
和 unlock()
方法编写正确的实现。
所以,问题是 - 处理这种情况的正确策略是什么?从目前的角度来看,我只看到三个选项(我觉得每个选项都有味道):
- 忽略
lock
/tryLock
/unlock
方法中的中断异常,重试/返回false/假设即使请求没有到达目的地,TTL最终也会解锁记录。这显然不是最好的解决方案,因为它希望一切都好,而不是处理问题。
- 包装
RuntimeException
继承人。这似乎也是一个糟糕的解决方案,因为客户端代码必须使用具体实现而不是原始接口,并且未经检查的异常当然不是为了那样的目的。
- 使用
force Thread.currentThread().interrupt()
调用。我不喜欢这种方式,因为它基本上告诉线程处理它自己的中断,而不是传递有关调用被中断的通知;另外,据我所知,如果没有外部轮询,它最终会产生线程,但不会立即处理中断,可能在完全不同的地方。
(当然,有一个选项允许客户端代码配置所需的行为,但这仍然没有为我提供一个真正好的解决方案)
有没有比我描述的更好的方法?如果不是,应该优先选择哪一个?
这完全取决于您的应用程序,特别是线程中 InterruptedException 的含义。
选项 1 对我来说是一个不好的做法:依赖其他机制会使您的代码不清晰和可怕。你总是可以捕获异常并只使用finally方法释放所有锁定的资源。
然后重新启动异常或隐藏它(可能返回特定结果)取决于您和该异常在您的应用程序中的具体含义。
让我讨论一下您的每一个可用选项。
- Ignore interrupted exception
这是错误的。当您实现其他用户将依赖的库之类的东西时,吞下异常永远是不对的。在这些情况下,吞下一个异常永远不会是明智的,除非你将它作为一个不同的异常传播,为客户端提供更有意义的信息。 InterruptedException
基本上是取消您的线程的请求,并且无论以后是否解锁锁,都不应从客户端抑制此信息。客户端需要知道有人希望停止此线程正在执行的工作单元。
- Wrap in
RuntimeException
没有。由于与上述完全相同的原因,这也是错误的。传播 InterruptedException
的原因是让客户端知道已发出取消正在执行的线程的请求,因此将其包装在 RuntimeException
中是错误的,因为此信息已丢失。
- 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)我才会选择中断后继续
我偶然发现了一种情况,我必须处理 InterruptedException
,但无法向上传递并且不觉得我的代码应该被允许吞下它。准确地说,我正在研究分布式锁实现,对支持服务的请求可能会中断甚至超时——当然,java.util.concurrent.Lock
不考虑这种情况,也不允许我吐出 InterruptedException。我正在努力为非抛出 lock()
、tryLock()
和 unlock()
方法编写正确的实现。
所以,问题是 - 处理这种情况的正确策略是什么?从目前的角度来看,我只看到三个选项(我觉得每个选项都有味道):
- 忽略
lock
/tryLock
/unlock
方法中的中断异常,重试/返回false/假设即使请求没有到达目的地,TTL最终也会解锁记录。这显然不是最好的解决方案,因为它希望一切都好,而不是处理问题。 - 包装
RuntimeException
继承人。这似乎也是一个糟糕的解决方案,因为客户端代码必须使用具体实现而不是原始接口,并且未经检查的异常当然不是为了那样的目的。 - 使用
forceThread.currentThread().interrupt()
调用。我不喜欢这种方式,因为它基本上告诉线程处理它自己的中断,而不是传递有关调用被中断的通知;另外,据我所知,如果没有外部轮询,它最终会产生线程,但不会立即处理中断,可能在完全不同的地方。
(当然,有一个选项允许客户端代码配置所需的行为,但这仍然没有为我提供一个真正好的解决方案)
有没有比我描述的更好的方法?如果不是,应该优先选择哪一个?
这完全取决于您的应用程序,特别是线程中 InterruptedException 的含义。
选项 1 对我来说是一个不好的做法:依赖其他机制会使您的代码不清晰和可怕。你总是可以捕获异常并只使用finally方法释放所有锁定的资源。
然后重新启动异常或隐藏它(可能返回特定结果)取决于您和该异常在您的应用程序中的具体含义。
让我讨论一下您的每一个可用选项。
- Ignore interrupted exception
这是错误的。当您实现其他用户将依赖的库之类的东西时,吞下异常永远是不对的。在这些情况下,吞下一个异常永远不会是明智的,除非你将它作为一个不同的异常传播,为客户端提供更有意义的信息。 InterruptedException
基本上是取消您的线程的请求,并且无论以后是否解锁锁,都不应从客户端抑制此信息。客户端需要知道有人希望停止此线程正在执行的工作单元。
- Wrap in
RuntimeException
没有。由于与上述完全相同的原因,这也是错误的。传播 InterruptedException
的原因是让客户端知道已发出取消正在执行的线程的请求,因此将其包装在 RuntimeException
中是错误的,因为此信息已丢失。
- 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)我才会选择中断后继续