在Java中处理InterruptedException异常

437

以下处理 InterruptedException 的方式有何区别?最佳方式是什么?

try{
 //...
} catch(InterruptedException e) { 
   Thread.currentThread().interrupt(); 
}

try{
 //...
} catch(InterruptedException e) {
   throw new RuntimeException(e);
}

编辑:我还想知道在什么情况下使用这两个。


3
http://www.javaspecialists.eu/archive/Issue056.html - starblue
7个回答

590
以下是处理InterruptedException的不同方式的区别?最佳方法是什么?
您可能会问这个问题,因为您调用了抛出InterruptedException的方法。
首先,您应该将throws InterruptedException视为方法签名的一部分,并且是调用您正在调用的方法的可能结果。因此,请接受InterruptedException是方法调用的完全有效结果的事实。
现在,如果您调用的方法抛出这样的异常,那么您的方法应该怎么做?您可以通过考虑以下问题来找到答案:
您正在实现的方法抛出InterruptedException是否有意义?换句话说,在调用您的方法时,InterruptedException是否是一个明智的结果?
  • If yes, then throws InterruptedException should be part of your method signature, and you should let the exception propagate (i.e. don't catch it at all).

    Example: Your method waits for a value from the network to finish the computation and return a result. If the blocking network call throws an InterruptedException your method can not finish computation in a normal way. You let the InterruptedException propagate.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
    
  • If no, then you should not declare your method with throws InterruptedException and you should (must!) catch the exception. Now two things are important to keep in mind in this situation:

    1. Someone interrupted your thread. That someone is probably eager to cancel the operation, terminate the program gracefully, or whatever. You should be polite to that someone and return from your method without further ado.

    2. Even though your method can manage to produce a sensible return value in case of an InterruptedException the fact that the thread has been interrupted may still be of importance. In particular, the code that calls your method may be interested in whether an interruption occurred during execution of your method. You should therefore log the fact an interruption took place by setting the interrupted flag: Thread.currentThread().interrupt()

    Example: The user has asked to print a sum of two values. Printing "Failed to compute sum" is acceptable if the sum can't be computed (and much better than letting the program crash with a stack trace due to an InterruptedException). In other words, it does not make sense to declare this method with throws InterruptedException.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Sum: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // set interrupt flag
             System.out.println("Failed to compute sum");
         }
    }
    
现在应该清楚,仅仅执行throw new RuntimeException(e)是一个坏主意。这不太客气。你可以发明一个新的运行时异常,但是根本原因(有人想要停止线程的执行)可能会丢失。
其他例子:
实现Runnable: 你可能已经发现了,Runnable.run 的签名不允许重新抛出InterruptedExceptions。那么,作为实现Runnable的人,这意味着你需要处理可能发生的InterruptedExceptions。要么选择一个不同的接口,比如Callable,要么按照上面的第二种方法去做。

 

Calling Thread.sleep: You're attempting to read a file and the spec says you should try 10 times with 1 second in between. You call Thread.sleep(1000). So, you need to deal with InterruptedException. For a method such as tryToReadFile it makes perfect sense to say, "If I'm interrupted, I can't complete my action of trying to read the file". In other words, it makes perfect sense for the method to throw InterruptedExceptions.

String tryToReadFile(File f) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        if (f.exists())
            return readFile(f);
        Thread.sleep(1000);
    }
    return null;
}

这篇文章已经被重写为这里的一篇文章。


6
文章指出,在调用interrupt()时需要保留中断状态。不在Thread.sleep()上这样做的原因是什么? - oksayt
9
如果您的线程不支持中断,任何接收到的中断都表示出现了意外情况(即编程错误、错误的 API 使用),抛出 RuntimeException 是一种适当的响应(快速失败)。除非在线程的总体约定中认为即使不支持中断也必须在收到中断时继续操作。 - amoe
22
调用会抛出InterruptedException的方法,并声称“不支持中断”,似乎是编程粗糙和隐患未爆发的一个BUG。换句话说,如果你调用一个声明会抛出InterruptedException的方法,那么当该方法确实抛出这个异常时,这不应该是一个意外情况。 - aioobe
1
@MartiNito,不,只在InterruptedException捕获块中调用Thread.currentThread.interrupt()将仅设置中断标志。它不会导致在外部InterruptedException捕获块中抛出/捕获另一个InterruptedException - aioobe
3
如果使用Thread.currentThread().interrupt(),请注意线程是否具有运行循环,该循环_不_检查/清除该标志。您可能需要添加一些代码来调用/检查Thread.interrupted()在顶层/循环中,以避免永久停留在中断状态。(例如,如果您有一个仅有一个地方捕获InterruptedException的循环,并且您的catch块调用Thread.currentThread.interrupt(),则如果没有从上一个调用中清除它,则随后的该代码调用将立即重新触发该异常。 - Mr Weasel
显示剩余6条评论

129

恰好今天早上在去上班的路上,我正在阅读Brian Goetz的Java并发实践,其中提到了这个问题。基本上他说你应该做以下三件事之一:

  1. 传播InterruptedException - 声明您的方法抛出已检查的InterruptedException,以便调用者必须处理它。

  2. 恢复中断 - 有时您无法抛出InterruptedException。在这种情况下,您应该捕获InterruptedException并通过在currentThread上调用interrupt()方法来恢复中断状态,以便调用堆栈中更高层次的代码可以看到发生了中断,并快速从方法返回。注意:仅当您的方法具有“尝试”或“最佳努力”语义时,即如果该方法未能完成其目标,则不会发生任何关键事件。例如,log()sendMetric()可能是这样的方法,或者boolean tryTransferMoney(),但不是void transferMoney()。有关更多详细信息,请参见here

  3. 在方法内忽略中断,但在退出时恢复状态 - 例如通过Guava的UninterruptiblesUninterruptibles接管类似于JCIP § 7.1.3中的Noncancelable Task示例中的样板代码。

13
有时候你不能抛出 InterruptedException。我会说,有时候方法传播 InterruptedException 是不合适的。你的表述似乎暗示着无论何时都应该重新抛出 InterruptedException。 - aioobe
2
如果你阅读第七章,你会发现有一些微妙之处,就像 7.7 节中的非可取消任务并不会立即恢复中断,而只有在完成任务后才会恢复。此外,Goetz 还有很多合著者为那本书做出了贡献... - Fizz
1
我喜欢你的回答,因为它很简洁明了。不过我认为在第二种情况下也应该指出要温和而快速地从函数中返回。想象一下一个长时间的(或无限的)循环,在其中有 Thread.sleep()。在 InterruptedException 中,应该调用 Thread.currentThread().interrupt() 并跳出循环。 - Yann Vo
@YannVo 我已经编辑答案,应用了您的建议。 - leventov

28

您想做什么?

InterruptedException 是在一个线程等待或休眠时,另一个线程使用 Thread 类中的 interrupt 方法中断它时抛出的异常。因此,如果捕获到此异常,表示该线程已被中断。通常情况下,除非您想从其他地方检查线程的“中断”状态,否则没有必要再次调用 Thread.currentThread().interrupt()

关于您另一种选项——抛出 RuntimeException,这似乎不是一个很明智的做法(谁来捕获它?如何处理它?),但如果没有更多的信息,就很难说了。


18
调用Thread.currentThread().interrupt()会重新设置中断标志,确保在更高层次上能够注意到并处理这个中断是非常有用的。请注意,这不会改变原始意思,只是使其更易于理解。 - Péter Török
1
@Péter:我只是在更新答案并提到这一点。谢谢。 - Grodriguez

16

正确的默认选择是将InterruptedException添加到抛出列表中。中断表示另一个线程希望结束你的线程。这个请求的原因没有明确说明,完全取决于上下文,因此如果你没有任何额外的知识,应该假设它只是一种友好的关闭,而任何避免这种关闭的行为都是不友好的响应。

Java不会随机抛出InterruptedException,所有建议都不会影响你的应用程序,但我遇到过一个开发人员遵循“吞掉”策略非常不方便的情况。一个团队开发了一组大量使用Thread.Sleep的测试。现在我们开始在我们的CI服务器上运行测试,有时由于代码缺陷会陷入永久等待状态。更糟糕的是,在尝试取消CI作业时,它永远不会关闭,因为Thread.Interrupt打算中断测试而没有中断作业。我们不得不登录到计算机并手动杀死进程。

长话短说,如果你简单地抛出InterruptedException,那么你就符合了你的线程应该结束的默认意图。如果你不能将InterruptedException添加到你的抛出列表中,我会将它包装在RuntimeException中。

有一个非常合理的论点可以认为InterruptedException本身应该是一个RuntimeException,因为这将鼓励更好的“默认”处理。它不是一个RuntimeException,仅仅因为设计者坚持一条分类规则,即RuntimeException应该表示你的代码中的错误。由于InterruptedException并不直接起源于你的代码中的错误,所以它不是RuntimeException。但现实情况是,中断通常会出现,因为你的代码存在错误(例如无限循环、死锁),而Interrupt是其他线程处理该错误的方法。

如果你知道有理性的清理工作要做,那么就去做吧。如果你知道中断的更深层次原因,你可以进行更全面的处理。

因此,总结一下,你处理的选择应该遵循以下列表:

  1. 默认情况下,添加到throws中。
  2. 如果不能添加到throws中,则抛出RuntimeException(e)。(在多个糟糕的选择中最好的选择)
  3. 只有当您知道中断的显式原因时,才能按照需要进行处理。如果您的处理仅限于方法内部,则通过调用Thread.currentThread().interrupt()来重置被中断状态。

你的总结很好(同时也是InterruptedException应该一开始就是RuntimeException的注释)。Thread.currentThread().interrupt()作为“默认处理机制”可能非常危险,会导致意外的忙等待,因此它只被正确地提到作为最后的选择,当你知道自己在做什么(以及为什么)时才应该这样做。 - mitchnull
顺便提一下,实现选项2的“优雅”方式是在您的方法中添加@SneakyThrows(InterruptedException.class) lombok注释 :) - mitchnull

14
对我来说,重要的是:InterruptedException不是任何错误,而是线程按照您告诉它要做的事情。因此,将其重新抛出包装在RuntimeException中是毫无意义的。
在许多情况下,当您说“我不知道这里出了什么问题,我无法修复它,我只想让它退出当前处理流程并命中我的应用程序范围异常处理程序以便记录它”时,重新抛出包装在RuntimeException中的异常是有意义的。但InterruptedException不是这种情况,它只是线程响应对其调用interrupt(),它抛出InterruptedException以帮助及时取消线程的处理。
因此,传播InterruptedException,或者在智能地消耗它(在可以实现预期目标的位置),并重置中断标志。请注意,当抛出InterruptedException时,中断标志会被清除;Jdk库开发人员的假设是捕获异常相当于处理它,因此默认情况下该标志被清除。
因此,第一种方式肯定更好,问题中发布的第二个示例除非您不希望实际中断线程,否则没有用处,而中断线程等同于错误。
以下是我写的一个答案,描述了中断如何工作,并提供了一个示例。您可以在示例代码中看到它在Runnable的run方法中使用InterruptedException来退出while循环。

5
我只想给大多数人和文章提到的内容增加一个选项。正如mR_fr0g所说,正确处理中断非常重要,可以通过以下方式之一来处理:
  • 传播InterruptException

  • 恢复线程上的中断状态

或者:

  • 自定义处理中断

根据您的情况以自定义方式处理中断是没有问题的。由于中断是终止请求而不是强制命令,因此完全可以完成其他工作以允许应用程序优雅地处理请求。例如,如果Thread正在睡眠、等待IO或硬件响应时收到Interrupt,则在终止线程之前优雅地关闭任何连接是完全有效的。

我强烈建议理解这个主题,但本文是信息的好来源:http://www.ibm.com/developerworks/java/library/j-jtp05236/


1
我认为在某些情况下不做任何事情是可以的。这可能不是默认情况下应该做的事情,但如果没有中断发生的可能性,我不确定还能做什么(可能记录错误,但这不会影响程序流程)。
一个例子就是当你有一个任务(阻塞)队列时。如果你有一个守护线程处理这些任务,并且你没有通过自己来中断线程(据我所知,jvm在关闭时不会中断守护线程),那么我看不到中断发生的可能性,因此可以忽略它。(我知道守护线程可能随时被jvm杀死,因此在某些情况下不适用)。
编辑: 另一种情况可能是保护块,至少基于Oracle的教程: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

1
这是错误的建议。我很难想象有什么任务如此重要,以至于可以忽略中断。Oracle可能只是懒得在Thread.sleep()调用中添加(更多)混乱。除非你知道为什么线程被中断,否则你应该停止你正在做的任何事情(要么返回,要么重新抛出异常以帮助你的线程尽快死亡)。 - Ajax
1
这就是为什么我说“有时候”。此外,因为它还没有被建议过,在我看来,在某些情况下完全可以。我想这取决于你正在构建什么样的软件,但如果你有一个24/7的工作线程,除了JVM关闭之外,它永远不应该停止,那么我会将从BlockingQueue中获取项目的中断视为错误(即记录),“不应该发生”,然后重试。当然,我会有单独的标志来检查程序终止。在我的一些程序中,JVM关闭不是正常操作下应该发生的事情。 - Bjarne Boström
1
换句话说,我认为更好的做法是冒险增加额外的错误日志记录(例如,在错误日志上发送电子邮件),而不是因InterruptedException 而导致应该24/7运行的应用程序停止,对此我在正常情况下看不到任何发生的可能性。 - Bjarne Boström
嗯,好吧,你的使用情况可能不同,但在理想的情况下,你不应该只是因为被中断就终止。你停止从队列中获取任务,并让那个线程死掉。如果线程调度器也被中断并被告知关闭,则JVM正在终止,游戏结束。特别是,如果你发送kill -9或者试图关闭你的邮件服务器或其他什么东西,那么它会忽略你直到你强制退出,从而防止你的代码的其他部分优雅地关闭。在写入磁盘或数据库时强制杀死进程,我相信会发生很多奇妙的事情。 - Ajax

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