【问题标题】:Handling InterruptedException in Java在 Java 中处理 InterruptedException
【发布时间】:2011-04-27 22:36:02
【问题描述】:

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

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

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

编辑:我也想知道这两个在哪些场景中使用。

【问题讨论】:

标签: java multithreading exception-handling interrupted-exception


【解决方案1】:

碰巧我今天早上在去Java Concurrency In Practice 工作的路上读到这篇文章,作者是 Brian Goetz。基本上他说你应该做三件事之一

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

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

  3. 忽略方法内的中断,但退出时恢复状态 - e. G。通过番石榴的UninterruptiblesUninterruptibles 接管样板代码,如 JCIP § 7.1.3 中的不可取消任务示例。

【讨论】:

  • “有时你不能抛出 InterruptedException”——我想说,有时候传播 InterruptedExceptions 的方法不合适。您的表述似乎建议您应该在可以时重新抛出 InterruptedException。
  • 如果你阅读第 7 章,你会发现其中有一些微妙之处,如代码清单 7.7 中的那些,其中不可取消的任务不会立即恢复中断 ,但只有在它完成之后。此外,Goetz 与这本书的许多合著者...
  • 我喜欢你的回答,因为它简洁,但我相信它也应该说明在第二种情况下从你的函数中轻轻快速地返回。想象一个长的(或无限的)循环,中间有一个 Thread.sleep() 。 InterruptedException 的捕获应调用 Thread.currentThread().interrupt() 跳出循环。
  • @YannVo 我已经编辑了答案以应用您的建议。
【解决方案2】:

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

你可能会问这个问题,因为你调用了一个抛出InterruptedException的方法。

首先,您应该看到throws InterruptedException 的含义:方法签名的一部分以及调用您正在调用的方法的可能结果。因此,首先要接受这样一个事实: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;
    }
    
  • 如果,那么你不应该用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;
}

此帖已改写为文章here

【讨论】:

  • 第二种方法有什么问题?
  • 文章说你应该调用interrupt()来保持中断状态。在Thread.sleep() 上不这样做的原因是什么?
  • 我不同意,在您的线程不支持中断的情况下,接收到的任何中断都表示意外情况(即编程错误、API 使用错误),并使用 RuntimeException 退出是适当的响应(快速失败)。除非它被认为是线程一般合同的一部分,即使您不支持中断,您也必须在收到中断时继续操作。
  • 调用抛出 InterruptedExceptions 并说您“不支持中断”的方法似乎是草率的编程和等待发生的错误。换一种说法;如果您调用一个声明为抛出 InterruptedException 的方法,如果该方法确实抛出了此类异常,那么它应该是一个意外情况。
  • @MartiNito,不,在 InterruptedException 捕获块中调用 Thread.currentThread.interrupt() 只会设置中断标志。它不会导致另一个InterruptedException 被抛出/捕获在外部InterruptedException 捕获块中。
【解决方案3】:

对我而言,关键在于:InterruptedException 并没有什么问题,它是线程按照您的要求执行的操作。因此,重新抛出它包装在 RuntimeException 中是零意义的。

在许多情况下,当您说,我不知道这里出了什么问题并且我无法修复它,我只是希望它退出时,重新抛出包含在 RuntimeException 中的异常是有意义的当前的处理流程并点击我拥有的任何应用程序范围的异常处理程序,以便它可以记录它。 InterruptedException 不是这种情况,它只是响应调用它的 interrupt() 的线程,它抛出 InterruptedException 以帮助及时取消线程的处理。

所以传播 InterruptedException,或者聪明地吃掉它(意思是在它完成它应该做的事情的地方)并重置中断标志。请注意,当抛出 InterruptedException 时,中断标志会被清除; Jdk 库开发人员所做的假设是捕获异常相当于处理它,因此默认情况下该标志被清除。

所以肯定第一种方法更好,问题中发布的第二个示例没有用,除非您不希望线程实际被中断,并且中断它会导致错误。

这是我写的答案describing how interrupts work, with an example。您可以在示例代码中看到它使用 InterruptedException 来摆脱 Runnable 的 run 方法中的 while 循环。

【讨论】:

    【解决方案4】:

    我想说,在某些情况下,什么都不做也没关系。默认情况下可能不是您应该做的事情,但如果中断不应该发生,我不确定还能做什么(可能会记录错误,但这不会影响程序流程)。

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

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

    【讨论】:

    • 这是个坏建议。我很难想到任何重要到忽略中断的任务。 Oracle 可能只是懒惰,不想在 Thread.sleep() 调用中添加(更多)混乱。除非你知道为什么你的线程会被中断,否则你应该停止你正在做的任何事情(要么返回,要么重新抛出异常以帮助你的线程尽快终止)。
    • 这就是我有时说的原因。也因为它还没有被建议并且在我看来在 某些 情况下它是完全可以的。我想这取决于你正在构建什么样的软件,但如果你有一个永远不应该停止的 24/7 工作线程(除了 JVM 关闭),我会处理来自例如的中断。将 BlockingQueue 中的项目作为错误(即日志),因为“它不应该发生”,然后重试。当然,我会有单独的标志来检查程序终止。在我的一些程序中,JVM 关闭不是正常操作下应该发生的事情。
    • 或者换一种说法:在我看来,冒险拥有额外的错误日志语句(例如在错误日志中发送邮件)比拥有一个应该运行 24/7 的应用程序更好由于中断异常而停止,我认为在正常情况下不会发生这种情况。
    • 嗯,好吧,您的用例可能会有所不同,但在理想情况下,您不会因为被打断而终止。你停止从队列中取出工作,让那个线程死掉。如果线程调度程序也被中断并被告知关闭,那么 JVM 将终止,并且它的游戏结束。特别是,如果您发送 kill -9 或以其他方式试图关闭您的邮件服务器或其他任何东西,那么它将忽略您,直到您强制退出,从而防止您的代码的其他部分正常关闭。在写入磁盘或数据库时强制终止,我相信会发生美妙的事情。
    【解决方案5】:

    我只是想在大多数人和文章提到的内容中添加最后一个选项。正如 mR_fr0g 所说,正确处理中断很重要:

    • 传播中断异常

    • 在线程上恢复中断状态

    或者另外:

    • 中断的自定义处理

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

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

    【讨论】:

      【解决方案6】:

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

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

      长话短说,如果您只是抛出 InterruptedException,您将匹配您的线程应该结束的默认意图。如果您无法将 InterruptedException 添加到您的抛出列表中,我会将其包装在 RuntimeException 中。

      有一个非常合理的论点是,InterruptedException 本身应该是一个 RuntimeException,因为这将鼓励更好的“默认”处理。这不仅仅是 RuntimeException,因为设计人员坚持一个明确的规则,即 RuntimeException 应该代表代码中的错误。由于 InterruptedException 不是直接由代码中的错误引起的,因此不是。但现实情况是,通常会出现 InterruptedException,因为您的代码中存在错误(即无限循环、死锁),而 Interrupt 是其他线程处理该错误的方法。

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

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

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

      【讨论】:

        【解决方案7】:

        你想做什么?

        InterruptedException 在线程等待或休眠并且另一个线程使用类Thread 中的interrupt 方法中断它时抛出。所以如果你捕捉到这个异常,就意味着线程已经被中断了。通常再次调用Thread.currentThread().interrupt(); 是没有意义的,除非您想从其他地方检查线程的“中断”状态。

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

        【讨论】:

        • 调用Thread.currentThread().interrupt()(再次)设置中断标志,如果我们想确保在更高级别上注意到并处理中断,这确实很有用。
        • @Péter:我只是在更新答案以提及这一点。谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-28
        • 2020-04-05
        • 2019-03-08
        • 1970-01-01
        • 2019-10-16
        • 1970-01-01
        相关资源
        最近更新 更多