如果没有人调用interrupt(),忽略InterruptedException是否可以?

52
如果我创建自己的线程(即非线程池),并且在某个地方调用sleep或其他可中断的方法,如果我知道代码中没有其他人对该线程进行中断,那么忽略InterruptedException是否可以呢
换句话说,如果该线程应该与JVM一起存在,表示该线程不可中断,那么可以安全地假设InterruptedException将永远不会被调用,因此可以吞掉该异常吗?

9
我认为实际问题是:JVM是否会在没有代码(通过Thread.interrupt()等)产生中断的情况下抛出InterruptedException? - MinecraftShamrock
1
是的,那是另一种表达方式。但我也想探讨“是否存在不可中断线程的用例”的方面。 - Hilikus
1
反过来问:“如果我总是正确处理InterruptedException,那么我的线程是否安全?” - 答案:不是。有一些无法中断的(API)例程,例如阻塞IO,这将使每个良好意图对InterruptedException都变得徒劳无功。因此,忽略InterruptedException通常不会破坏任何东西,即它是“安全”的。正如其他人所说,虽然这很少“令人满意”。 - JimmyB
1
一些不直接“处理”您的代码的容器/运行时包装可能需要中断线程。例如,我正在使用Maven exec插件工作,当我进行更改以吞噬中断异常时,当我尝试使用sigterm或^C停止程序时,Maven会出现问题。 - nanofarad
8个回答

38
忽略已检查异常从未被认为是安全的。在某些情况下,对您来说可能看起来没问题,但是如果其他程序员使用您的代码/API,则应该期望标准行为: 即线程“响应”中断调用,但具体的“响应”取决于线程文档的说明。这意味着线程的开发人员需要确定并记录线程如何处理中断,并且Java没有任何预定义的规则,但您应该记录线程的“响应”(使用文档注释)。例如,InterruptedException仅从阻塞方法(如Thread.sleep(...))抛出,而不会从正常的代码行抛出(这意味着默认情况下将忽略中断),这使得开发人员可以选择(和记录)线程的响应方式,例如: - 可能通过取消正在进行中的任务来中止。 - 可能通过在catch块中调用Thread.currentThread()。interrupt()来推迟(或软忽略)。 - 可能通过抛出java.lang.Error(或子类)并传递InterruptedException实例作为cause来崩溃。除非有记录,否则这是最不理想或不期望的。 要解释"推迟"; 对于InterruptedException,空catch块很危险,因为JVM会删除中断标志,因此在catch块中一定要重新设置它,如下所示:
try {
    Thread.sleep(1000);
} catch (InterruptedException ignored) {
    Thread.currentThread().interrupt();
}
在我看来,这是InterruptedException的最小捕获实现。在循环中检查isInterrupted标志不会带来太多额外开销。与您的未来程序员自己为寻找意外线程行为而烦恼一两天相比,这点开销微不足道,因为您的项目可能已经增长了一些。
如果您觉得这些捕获实现影响代码的可读性,可以实现自己的safeSleep实用程序方法,该方法负责处理Exception并正确设置标志。
另一方面,InterruptedException在硬件故障的情况下JVM本身不会抛出,它只是用户指定的Exception。那就技术上是这样的。但是,不应低估人为因素和您的程序演变。
编辑:正如ruakh所指出的那样,实际上有一种方法可以获取Thread的引用,并因此安排Thread.interrupt()调用。这种方式,发热的开发者甚至可能不必查看实现您不可中断的Thread的类。在我看来,这甚至是实现适当异常处理的另一个理由。另一件事:如果您不抛出异常,那么将此类事件记录在高于INFO级别的日志中可能是一个不错的选择。

3
“标准行为”是什么?指的是清理和关机吗? - Solomon Slow
2
回复:“如果您不传播Thread的引用,就不会有其他Thread能够调用它的Thread.interrupt()方法。”线程不像随意的对象,您可以决定是否传播。JDK提供了查找所有线程的方法。请参见https://dev59.com/TXM_5IYBdhLWcg3wjkCr,了解一些方法。 - ruakh
关于 safeSleep 方法:你可以使用 Guava 的 Uninterruptibles(例如 Uninterruptibles.sleepUninterruptibly)而不是自己实现。 - randers
另一方面,JVM本身不会抛出InterruptedException。您能否通过链接到说明文档/规范来支持这个说法? - Eduard Wirch
@EduardWirch 我想我是从迈克尔·因登(Michael Inden)的一本德语书中得到它的,但我现在手头没有。但是一次快速搜索带出了这个SO-answer,其中引用了JLS第17.2.3节:“中断操作发生于调用Thread.interrupt以及定义调用它的方法,例如ThreadGroup.interrupt。” - Zhedar
显示剩余2条评论

34

如果你确信某件事永远不会发生,那么与其忽略它,不如选择崩溃。比如,抛出一个Error(或者RuntimeException):

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    throw new Error(e);
}

Error的Javadocs中得知:

Error是Throwable的一个子类,表示严重问题,合理的应用程序不应该试图捕获大多数此类错误都是异常情况。

如果您认为假设某些事情永远不会发生有价值,那么如果它确实发生了,那就是一种"异常情况",可能会成为一个"严重问题"。

另一种看待这个问题的方法是,如果将来的某个时刻有人打断了您的方法,他们是否希望它继续运行,就好像什么都没发生过?我想答案是否定的。

延迟处理

另一个替代方案是延迟处理,例如:

try {
    Thread.sleep(1000);
} catch (InterruptedException ignored) {
    Thread.currentThread().interrupt();
}

// ... do some work ...

if (Thread.currentThread().isInterrupted()) {
    // Handle if possible, and was expected,
    // else, throw new `InterruptedException`.
}

请注意:上面的示例代码没有执行"if (Thread.interrupted()) {",因为那会清除线程的中断标记(这是一个不好的模式)。


9
йҖҡеёёжҲ‘дјҡдҪҝз”ЁAssertionErrorжқҘеӨ„зҗҶиҝҷдёӘй—®йўҳпјҢдёҚиҝҮиҝҷеҸӘжҳҜдёӘдәәеҒҸеҘҪгҖӮ - user253751
2
然后,半年后当它崩溃了,因为你写了一些需要打断它的东西,你会记得为什么一开始决定不悄悄地吞掉错误。或者,你可能会有一个微妙的 bug,比如数据没有保存到数据库、内存泄漏或其他该线程可能持有的资源。 - sfdcfox
1
这太丑陋了,如果你要重新抛出 InterruptedException,就直接抛出 InterruptedException 本身。对于库代码来说尤其如此。如果这是你的应用程序代码,无论你是重新抛出还是忽略异常,实际上都不会有任何区别,因为你可以保证它根本不是在第一次引起的。另一个选择是优雅地退出你的线程。 - Dorus
3
@Dorus,这个问题是关于在线程内编写的代码的。除非它被包装在其他东西中,否则你不能从Runnable.run()抛出InterruptedException。如果你的意思是“在某个方法内抛出InterruptedException,然后调用该方法”,那么当调用它的线程发生什么情况时,你会怎么做呢? - Dan Getz
没错,一旦你到达线程级别,崩溃或优雅地结束你的线程都是有效的选择。我同意。选择最适合你的选项。 - Dorus

18

如果您认为异常不应该发生,那么就不要将其吞咽。可以决定不添加处理永远不会发生的条件的代码,但不应该默默无闻地发生异常。

你至少应该做到:

catch(InterruptedException ex) {
  throw new AssertionError(ex);
}

这样可以确保只要您断言“这永远不会发生”的错误,就会注意到。该模式还适用于其他意外异常,例如当您知道目标OutputStreamByteArrayOutputStreamFormatterAppendable应该是StringBuilder等,并出现IOException


顺便说一下,有一种替代方法可以完全不需要处理InterruptedException,即:Thread.sleep

LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(timeInMillis));

当线程被中断时,此方法将简单地返回早期,并保留Thread的中断状态,因此对于使用中断的其他人使用代码的情况(假设调用者检查了中断状态),它已经提供了正确的处理方式。


1
此外,如果您有理由相信您的代码可以安全地忽略某些异常,那么您至少应该调用LOGGER.debug(ex),这样如果您的信念最终被证明是错误的,那么弄清楚发生了什么就会更容易。 - Solomon Slow
1
@jameslarge 对我来说,仅仅打印日志信息仍然忽略了异常。 - Hilikus
@Hilikus,我同意。Holger的回答解释了为什么忽略你认为永远不会发生的异常是一个坏主意。我在我的评论中补充说了一些关于罕见情况的事情,即你知道异常将会发生,并且你有意忽略它。 - Solomon Slow
1
+int(PI/3) 用于 LockSupport - 你让我今天又学会了 Java 中不太常见的特性... - user719662

14

我依然认为Goetz等人的《Java并发编程实践》是这些问题的圣经,在第七章中提到了以下重要内容(Goetz的developerWorks文章基本上涵盖了相同的材料,但书中在一些方面更加清晰明了)。

中断是一种合作机制。一个线程不能强制另一个停止正在做的事情并去做其他事情;当线程A中断线程B时,只是请求B在方便的停止点停下它正在做的事情 - 如果它觉得需要停下来的话。

[...]

只有实现线程中断策略的代码才能吞噬中断请求。通用任务和库代码不应该吞噬中断请求。

所以,在你概述的情况下,你可以“吞掉” InterruptedException异常。

此外,书中还有一个重要的观点:

由于每个线程都有自己的中断策略,除非你知道中断对该线程意味着什么,否则不应该中断线程。

所以你可以选择自己的策略,比如使用RuntimeExceptionAssertionError来“崩溃”,只将其记录为警告,或完全忽略中断,只要你为其他需要知道此问题的人记录下来。最好的选择取决于你的线程实际在做什么,而你没有提供相关细节信息。例如,如果它正在火箭上运行[比如进行姿态控制],你不想因某个同事程序员[他可能甚至不是你们公司的人,例如他编写了一个库供你调用]在他的代码中忘记了某个合同并在你的代码上产生了一个安全可忽略的异常而导致JVM/火箭崩溃:“所发生的异常不是由于随机故障引起的,而是由于设计错误引起的。”

8

这个问题不能忽略。在抛出InterruptedException时,要么将其返回给调用者,要么重新断言线程的中断状态,因为静态状态会在抛出异常后被清除:

} catch (InterruptedException e) {
    // Restores the interrupted flag status.
    Thread.currentThread().interrupt();
}

除非你在捕获异常后立即退出线程。

这里是一篇关于处理InterruptedException的文章。

例子

InterruptedException仅从阻塞方法(如Thread.sleep(...))抛出,而从正常代码行中永远不会抛出, 程序员需要决定线程如何响应中断。

例如,有一个项目只是对它进行了文档描述:

public class MyTask {

// ...

/**
 * Waits for result to be use ready.
 * <br>
 * <br>
 * <b>Warning:</b> instead of throwing {@link InterruptedException},
 * returns {@code null}, and if that is undesired, do something like:
 *
 * <pre>{@code
 * MyResult result = myTask.await(...);
 * if (Thread.currentThread().isInterrupted()) {
 *     throw new InterruptedException();
 * }
 * }</pre>
 *
 * @param timeout Duration to wait at most.
 * @param unit    Type of duration.
 */
public MyResult await(long timeout, TimeUnit unit) {
    try {
        return /* ... */;
    } catch (InterruptedException e) {
        // Restores the interrupted flag status.
        Thread.currentThread().interrupt();
    }
    return null;
}

}

请注意,上面的文档块代码没有执行if (Thread.interrupted()) {,因为这会清除线程上的中断标志(这是一种不好的模式)。

2
如果你只是重新抛出异常,那么这岂不与使用已弃用的Thread.stop()方法终止线程有着相同的效果吗?谁会捕获这个异常? - kapex

7

如果您的项目变得越来越复杂,那么很难肯定没有人会调用中断方法。如果有一天有人调用此方法并发生InterruptedException,那么如果您没有至少在某个地方记录它,将会非常难以调试。


2
如果线程的寿命与JVM一样长,也就是说线程不可中断,那么可以安全地假定InterruptedException永远不会被抛出,因此异常可以被吞咽。如果您要吞掉异常的代码完全在您的控制下,并且如果它只是客户端代码(它永远不会在您自己以外的上下文中重用),那么可以安全地吞掉异常。请注意,这里有很多如果。如果您恰好使用Java 8,则有另一种选择,在这里描述:将您的代码包装在一个uncheckCall(() -> { ... })块中。这使得代码块可以抛出InterruptedException而不声明它。如链接答案所述,这应该非常小心地处理,但它有潜力使您的代码非常干净。

1
我认为忽略InterruptedException是否安全,取决于您在该线程中正在执行的操作。如果有任何情况,当您的线程遭受不必要的中断时,可能会使系统或任何资源处于不需要的状态(脏、锁定、未释放,可能导致资源泄漏),那么处理InterruptedException是您的责任,您不应该忽略它。您可以选择使您的线程可中断或不可中断。
换句话说,中断机制主要用于实现取消策略和任务清理。如果您没有任何任务清理或特定的任务取消策略,则忽略InterruptedException可能听起来不太正确,但是也可以接受。

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