在Java中处理InterruptedException


317

以下处理方式之间有什么区别InterruptedException?最好的方法是什么?

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

要么

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

编辑:我也想知道这两种情况在哪些情况下使用。


Answers:


436

以下处理InterruptedException的方式之间有什么区别?最好的方法是什么?

您可能会问这个问题,因为您已经调用了throw方法InterruptedException

首先,您应该throws InterruptedException了解它的含义:方法签名的一部分,以及调用所调用方法的可能结果。因此,首先要考虑一个事实,即an InterruptedException是方法调用的完全有效结果。

现在,如果您正在调用的方法抛出此类异常,那么您的方法应该怎么做?您可以通过考虑以下因素找出答案:

要实现的方法抛出一个有意义InterruptedException吗?换句话说,InterruptedException调用您的方法是否明智?

  • 如果,那么throws InterruptedException应当成为你的方法签名,你应该让异常繁殖(即完全不抓住它)。

    示例:您的方法等待网络中的值来完成计算并返回结果。如果阻塞的网络调用引发InterruptedException您的方法无法正常完成计算。你让InterruptedException传播。

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • 如果为no,则不应使用声明您的方法,throws InterruptedException并且(必须!)应捕获异常。现在,在这种情况下要记住两件事:

    1. 有人打断了您的线程。有人可能想取消操作,优雅地终止程序或执行其他操作。您应该对那个人彬彬有礼,并毫不费力地从方法中返回。

    2. 即使在线程被中断的情况下,即使您的方法可以设法产生合理的返回值,InterruptedException也可能仍然很重要。特别是,调用您的方法的代码可能会对在执行方法期间是否发生中断感兴趣。因此,您应该通过设置中断标志来记录发生中断的事实:Thread.currentThread().interrupt()

    示例:用户要求打印两个值的总和。Failed to compute sum如果无法计算总和,则打印“ ”是可以接受的(并且比使程序因栈跟踪而崩溃更容易InterruptedException)。换句话说,使用声明此方法没有任何意义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,或按照上面的第二种方法。

 

呼叫Thread.sleep:您正在尝试读取文件,而规范说您应该尝试10次,中间间隔1秒。你打电话Thread.sleep(1000)。因此,您需要处理InterruptedException。对于这样的方法tryToReadFile,说“如果我被打断了,我将无法完成尝试读取文件的动作”是很有意义的。换句话说,该方法抛出异常是有意义的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;
}

这篇文章在这里已被重写为文章。


第二种方法有什么问题?
blitzkriegz

4
文章说您应该致电interrupt()以保留中断状态。不对a执行此操作的原因是什么Thread.sleep()
oksayt 2011年

7
我不同意,在您的线程不支持中断的情况下,收到的任何中断都表明出现了意外情况(例如,编程错误,错误的API使用),并且使用RuntimeException进行应急处理是一种适当的响应(快速失败) )。除非被认为是线程总协定的一部分,否则即使您不支持中断,您也必须在收到中断时继续操作。
amoe

12
调用引发InterruptedExceptions并说您“不支持中断”的方法似乎像草率的编程和等待发生的错误。换句话说。如果调用,则声明抛出InterruptedException的方法,应该不会是一个意外情况如果该方法实际上抛出这样的例外。
aioobe 2015年

1
@MartiNito,不,Thread.currentThread.interrupt()InterruptedExceptioncatch块中调用只会设置中断标志。不会导致其他InterruptedException人被扔/夹在外部InterruptedException挡块中。
aioobe

97

碰巧的是,我今天早上刚刚读到Brian Goetz 在Java Concurrency In Practice中工作的方式。基本上他说你应该做三件事之一

  1. 传播InterruptedException -声明您的方法以引发选中项,InterruptedException以便您的调用者必须对其进行处理。

  2. 恢复中断 -有时您无法抛出InterruptedException。在这些情况下,您应该InterruptedException通过在interrupt()方法上调用方法来捕获和恢复中断状态,currentThread以便调用堆栈上方的代码可以看到已发出中断,并可以从方法中快速返回。注意:这仅在您的方法具有“尝试”或“尽力而为”语义时才适用,即,如果该方法未实现其目标,则不会发生任何严重的情况。例如,log()sendMetric()可能是这种方法,或boolean tryTransferMoney(),但不是void transferMoney()。有关更多详细信息,请参见此处

  3. 忽略方法中的中断,但是在退出时恢复状态 -例如通过Guava的UninterruptiblesUninterruptibles像JCIP第7.1.3节中的“不可取消任务”示例那样接管样板代码。

10
“有时您不能抛出InterruptedException” –我会说,有时该方法不适合宣传InterruptedExceptions。您的配方似乎表明,你应该重新抛出InterruptedException的,只要你
aioobe 2014年

1
而且,如果您要阅读第7章,将会看到一些细微之处,如清单7.7所示,其中不可取消的任务不会立即恢复中断,而只是在完成后才恢复。格茨(Goetz)也有很多共同作者……
菲兹(Fizz)

1
我喜欢您的答案,因为它简洁明了,但是我认为在第二种情况下,它也应该说明从您的函数中缓慢而迅速地返回。想象一个长(或无限)循环,中间有一个Thread.sleep()。捕获InterruptedException应该调用Thread.currentThread()。interrupt()退出循环。
Yann Vo,

@YannVo我已编辑答案以应用您的建议。
leventov '19

18

你想做什么?

InterruptedException抛出时,一个线程正在等待或睡眠,并使用另一个线程中断它interrupt在类方法Thread。因此,如果捕获到此异常,则意味着线程已被中断。通常,Thread.currentThread().interrupt();再次调用没有任何意义,除非您要从其他地方检查线程的“中断”状态。

关于抛出a的其他选择RuntimeException,这似乎不是一件很明智的事情(谁会抓到这个?如何处理?),但是如果没有其他信息,很难告诉更多。


14
调用Thread.currentThread().interrupt()设置了中断标志(再次),这确实很有用,如果我们要确保更高级别注意到并处理中断。
彼得Török

1
@Péter:我只是在更新答案以提及这一点。谢谢。
Grodriguez

12

对我来说,关键的是:InterruptedException没什么错,它是线程按照您的指示执行的。因此,将其重新包装在RuntimeException中具有零意义。

在很多情况下,当您说“我不知道这里出了什么问题,我无法采取任何措施来解决它,我只是想让它脱离当前的处理流程”时,抛出一个包裹在RuntimeException中的异常是有意义的并点击我拥有的所有应用程序范围的异常处理程序,以便可以对其进行记录。使用InterruptedException并不是这种情况,它只是响应调用了interrupt()的线程,它抛出InterruptedException是为了帮助及时取消线程的处理。

因此,传播InterruptedException或聪明地吃掉它(意味着在可以完成其应做的工作的地方)并重置中断标志。请注意,当抛出InterruptedException时,中断标志将被清除。Jdk库开发人员所做的假设是捕获异常就等于对其进行处理,因此默认情况下会清除该标志。

因此,肯定第一种方法更好,问题中的第二个示例无济于事,除非您不希望线程实际被中断,并且中断线程将导致错误。

这是我写的一个答案,其中举例说明了中断的工作方式。您可以在示例代码中看到它使用InterruptedException来摆脱Runnable的run方法中的while循环的麻烦。


10

正确的默认选择是将InterruptedException添加到您的引发列表。中断表示另一个线程希望您的线程结束。发出此请求的原因并不明显,并且完全是上下文相关的,因此,如果您没有任何其他知识,则应假定这只是友好的关闭,而避免关闭的任何操作都是不友好的响应。

Java不会随机抛出InterruptedException,所有建议都不会影响您的应用程序,但是我遇到了这样一个情况,即开发人员遵循“吞咽”策略非常不便。一个团队开发了很多测试并大量使用了Thread.Sleep。现在,我们开始在CI服务器中运行测试,有时由于代码缺陷而会陷入永久等待。更糟的是,当尝试取消CI作业时,它从未关闭,因为旨在中止测试的Thread.Interrupt并未中止该作业。我们必须登录到该框并手动终止进程。

长话短说,如果仅抛出InterruptedException,那么您正在匹配线程应结束的默认意图。如果您不能将InterruptedException添加到抛出列表,则将其包装在RuntimeException中。

有一个非常合理的论据认为InterruptedException应该本身就是RuntimeException,因为这会鼓励更好的“默认”处理。这不是RuntimeException的唯一原因是设计人员坚持使用分类规则,即RuntimeException应该代表代码中的错误。由于InterruptedException并非直接源于您的代码中的错误,因此不是。但是现实情况是,由于代码中存在错误(例如,无限循环,死锁),经常会出现InterruptedException,而Interrupt是处理该错误的其他一些线程的方法。

如果您知道要进行合理的清理,那就去做。如果您知道造成中断的更深层原因,则可以进行更全面的处理。

因此,总而言之,您的处理选择应遵循以下列表:

  1. 默认情况下,添加为throws。
  2. 如果不允许添加,则抛出RuntimeException(e)。(多个坏选项的最佳选择)
  3. 仅当您知道明显的中断原因时,才按要求进行处理。如果您的处理是方法的本地处理,则通过调用Thread.currentThread()。interrupt()来中断重置。

3

我只是想在大多数人和文章中提到的最后一个选项。正如mR_fr0g所述,通过以下方法正确处理中断很重要:

  • 传播InterruptException

  • 恢复线程上的中断状态

或另外:

  • 定制处理中断

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

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


0

我会说在某些情况下什么都不做是可以的。默认情况下,可能不是您应该执行的操作,但是如果没有办法发生中断,则不确定要执行其他操作(可能记录错误,但这不会影响程序流)。

一种情况是万一您有一个任务(阻塞)队列。如果您有一个守护程序线程来处理这些任务,并且您自己没有中断线程(据我所知,jvm在jvm关闭时不会中断守护程序线程),我看不到发生中断的方法,因此可能是只是忽略了。(我知道守护进程线程可能随时被jvm杀死,因此在某些情况下是不合适的)。

编辑:另一种情况可能是受保护的块,至少基于Oracle的教程位于:http : //docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html


这是个坏建议。在考虑任何重要的任务以至于忽略中断时,我都会遇到极大的麻烦。Oracle可能只是懒惰而已,不想给Thread.sleep()调用增加(更多)混乱。除非您知道线程被中断的原因,否则您应该停止正在执行的任何操作(返回或抛出异常以帮助您的线程尽快死亡)。
阿贾克斯

这就是为什么我有时说。同样是因为还没有建议,在我看来,在某些情况下它还可以。我想这取决于您所构建的软件类型,但是如果您有一个永远不应该停止的24/7工作线程(除了JVM关闭),我会将中断(例如,从BlockingQueue中获取项目作为错误)视为错误(即日志),因为“不应发生”,然后重试。当然,我将有单独的标志来检查程序终止。在我的某些程序中,在正常操作下不应该关闭JVM。
BjarneBoström'16

或换种说法:在我看来,冒一个额外的错误日志语句(例如,在错误日志上发送邮件)的风险要比由于中断异常而应运行24/7的应用程序停止运行的风险更大,为此,我认为在正常情况下不会发生任何事情。
BjarneBoström'16

嗯,您的用例可能有所不同,但是在理想的情况下,您不只是因为被打扰而终止。您停止将工作从队列中移开,让该线程死亡。如果线程的调度程序也被中断并被告知关闭,那么JVM将终止,并且其游戏结束。特别是,如果您发送kill -9或试图关闭邮件服务器或其他任何东西,那么它将在您强制退出之前将忽略您,从而阻止了其他代码段的正常关闭。在写入磁盘或数据库时强行杀死,我敢肯​​定,将会发生美好的事情。
阿贾克斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.