【问题标题】:When should a method throw InterruptedException, and how should I handle one that does? (blocking method)一个方法应该在什么时候抛出 InterruptedException,我应该如何处理它呢? (阻塞方法)
【发布时间】:2012-06-07 18:08:28
【问题描述】:

如果一个方法必须是一个阻塞方法,我是否认为如果我离开 出throws InterruptedException,我搞错了吗?

简而言之:

  • 阻塞方法应包含throws InterruptedException,否则为普通方法。
  • 阻塞方法可能会影响响应能力,因为很难预测它何时会完成,这就是它需要 throws InterruptedException 的原因。

对吗?

【问题讨论】:

  • 下面的答案有帮助吗?如果是这样,您应该接受它作为答案。

标签: java multithreading exception-handling blocking


【解决方案1】:

如果您的方法阻塞,它应该捕获并处理InterruptedException,并且不希望重新抛出它。

此外,该方法可能会在多个地方阻塞 - 每个地方都应该以适合可能抛出它的地方的方式捕获和处理 InterruptedException。

关于多线程代码的圣经是Java Concurrency in Practice。我强烈建议您阅读它。

编辑:

在设计并发代码时,请意识到:

  • 根据 JVM 规范,InterruptedException 可能会被 JVM 无缘无故地随机抛出(称为“虚假唤醒”)
  • 许多线程可能在相同的条件下等待,所有线程都可能被唤醒(例如由notifyAll()),但在中断时只有一个线程可以前进

所以每当一个线程被唤醒时,它应该检查它正在等待的等待条件的状态,并且可能回到等待状态。

因此,正确编写的并发代码应该会捕获InterruptedException。您可以选择重新抛出它或抛出您自己的应用程序特定异常。 “应用程序代码”方法应该更喜欢抛出“应用程序”异常,但是如果您等待的代码可能会发现自己处于无法弄清楚“出了什么问题”的状态,那么您唯一的选择就是抛出 InterruptedException。

【讨论】:

  • 这个建议对我来说听起来倒退了。您提出的捕获和吞噬InterruptedException 的这种方法需要某种方式向其调用者指示它无法完成预期的操作,因为它在完成之前就被中断了。您如何建议该方法向其调用者指示该条件?
  • 我认为这取决于您的应用程序中的哪一层能够正确处理中断; “正确”在这里是主观的,它取决于系统特有的许多因素。
  • @Zaki 它不“取决于哪个层处理异常”。这种选择与 API 设计无关:正在等待的方法对其调用者一无所知。选择哪个层捕获异常是由调用者做出的,而不是被调用的方法。您可以从方法中抛出异常,但不要抛出低级线程异常。不管你扔什么,决定哪一层处理它。有关更多解释,请参阅编辑后的答案
  • JDK 中几乎所有的阻塞方法都会抛出 InterruptedException。而且您将虚假唤醒与“虚假异常”(不存在,AFAIK)混淆了。圣经所说的正是当发生 InterruptedException 而你不知道如何处理时,最好的办法就是传播它。重新阅读 JCIP 的第 7.1.3 节。
  • 我还是觉得你的回答是完全错误的。 InterruptedException 是最好的抛出异常,表示由于线程被中断而无法完成工作。并且不存在虚假的中断。 JVM 可能不随机抛出 InterruptedException。
【解决方案2】:

InterruptedException(通常)在方法上阻塞的线程被调用 interrupt() 时被抛出。

它的目的是解除阻塞(出于某种原因)被阻塞的线程。原因的示例是应用程序关闭。所以,当你关闭你的应用程序时,如果你有线程在等待让我们说sleep() 或wait(),如果你不告诉他们你正在关闭他们将继续wait()。如果这些线程不是守护线程,那么您的应用程序将不会关闭。

因此,当线程在sleep() 期间被中断时,您必须检查条件并处理这种情况。在关闭的情况下,您必须检查您的shutdown 标志并最终进行清理工作并让线程离开。

线程可以因为其他一些原因而被中断,但要点是一样的。 如果您有多线程应用程序,您必须为您的线程建立协议,以便他们知道何时有一些特殊情况如何处理它。如果线程正在等待/休眠,您必须将其唤醒以处理这种情况。 您的库或框架的客户对您的协议一无所知,因此他们不知道如何处理InterruptedException,因为建议在您的库/框架代码中处理它。

【讨论】:

    【解决方案3】:

    不,我认为您的摘要不正确。 通常,如果您正在编写一个调用其他抛出 InterruptedException 的方法,那么您的方法也应该宣传抛出 InterruptedException — 除非您有一个很好的计划你的依赖信号中断。

    您能够接受这种干扰的情况很少见。也许您正在计算一个迭代解决方案,其中精度随着时间的推移而增加,但是,在您的调用线程被中断时,您认为您在分配的时间内达到的解决方案已经足够好,并且仍然足够正确以返回。换句话说,该解决方案仍在您的方法的范围内。

    想象一下:

    private double improveUpon(double start) throws InterruptedException {
      // ...
    }
    
    
    public double compute() {
      double result = 0.0;
      try {
        do {
          result = improveUpon(result);
        } while (couldBeImproved(result));
      } catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
      }
      return result;
    }
    

    或者,如果您只是想尊重中断请求,您可以在不涉及InterruptedException 的情况下这样做:

    private double improveUpon(double start) {
      // ...
    }
    
    
    public double compute() {
      final Thread current = Thread.currentThread();
      double result = 0.0;
      do {
        result = improveUpon(result);
      } while (couldBeImproved(result) &&
               !current.isInterrupted());
      return result;
    }
    

    对于另一种变体,请考虑以下情况:您的方法必须完成其所有工作或向调用者指示它无法完成它,并且需要一段时间才能到达那里,但您希望尊重线程中断。像这样就足够了:

    private double improveUpon(double start) {
      // ...
    }
    
    
    public double compute() throws InterruptedException {
      final Thread current = Thread.currentThread();
      double result = 0.0;
      do {
        if (current.interrupted())
          throw new InterruptedException();
        result = improveUpon(result);
      } while (!isAdequate(result));
      return result;
    }
    

    请注意,我们调用了Thread#interrupted(),其副作用是清除线程的中断状态(如果已设置)。如果该方法返回 true,我们作为调用者已经接受了保持和传达中断状态的责任。在这种情况下,由于我们没有假设我们创建了调用线程,并且我们在这里没有足够的可见范围来了解它的中断策略是什么,所以我们通过抛出 InterruptedException 来传达我们观察和采用的中断状态。

    将方法标记为“阻塞”始终是程度问题;每个方法都会阻塞它的调用者一段时间。您可能正在寻找的区别是该方法是否阻止等待某些外部输入,例如用户按键或通过网络到达的消息。在这些情况下,您抛出InterruptedException 的广告向您的调用者表明,您的方法可供来自必须控制其延迟的线程的调用者使用。你是说,“这可能需要一段时间才能完成,但不会比你愿意等待的时间长。”你在说,“我会一直跑,直到你告诉我不要。”这与java.io.InputStream#read() 不同,后者威胁要阻塞直到三个条件之一发生,其中没有一个调用者的线程被中断。

    在大多数情况下,您的决定归结为回答以下问题:

    • 为了满足我的方法的要求,我是否需要调用任何抛出 InterruptedException 的方法?
    • 如果是这样,我到那时为止所做的工作对我的调用者有用吗?
    • 如果没有,我也应该抛出InterruptedException。
    • 如果我没有调用任何东西抛出 InterruptedException,我应该尊重我的调用线程的中断状态吗?
    • 如果是这样,我是否已经完成了任何工作,直到我检测到我的呼叫者对我的任何使用都被打断了?
    • 如果不是,我应该抛出InterruptedException。

    检测到当前线程的中断并吞下它的情况通常仅限于您(作者)创建了有问题的线程,并且您已承诺一旦线程获取退出线程的run() 方法打断了。这就是“合作取消”的概念,其中您观察 request 让您的线程停止运行,并决定通过尽快完成工作并让线程的调用堆栈来遵守该请求放松。同样,除非您是线程 run() 方法的作者,否则您吞下线程的中断状态可能会损害您的调用者以及他们调用的其他方法的预期行为。

    我建议你研究线程的中断状态这个话题,并熟悉Thread#isInterrupted()、Thread#interrupted()和Thread#interrupt()这些方法。一旦你理解了这些,并看到 InterruptedException 在飞行中是 Thread#isInterrupted() 返回 true 的替代表示,或者 Thread#interrupted() 返回 true 的礼貌翻译,这一切都应该开始变得更有意义了。

    如果你需要更多的例子来学习,请说出来,我可以在这里添加建议。

    【讨论】:

    • 您的示例太琐碎,无法探索多线程代码的微妙之处。想象一下正在等待资源可用的代码——许多线程可能正在等待,并且所有线程都可能被唤醒,但只有一个线程先进入,所以其他线程必须继续等待。您的示例没有显示这一点-它只是在中断时停止,这仅在最微不足道的示例中是可以接受的。此外,您应该阅读“虚假唤醒” - 您没有满足这些要求,因为您没有等待条件。您对“所有方法阻塞”的评论是幼稚和错误的:“阻塞”意味着“可能永远阻塞”
    • 我非常了解虚假唤醒,它们与本次讨论无关。在我的示例中,您在哪里看到容易受到虚假唤醒的调用?
    • @Bohemian:虚假唤醒与 InterruptedExceptions 无关。当wait() 在没有任何notify 调用的情况下返回时,会发生虚假唤醒。当在可中断阻塞调用上阻塞的线程被中断时,会发生 InterruptedException。
    • 非常棒的 +1。你要问自己的最终问题清单是完美的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 2012-03-23
    • 2010-12-31
    • 1970-01-01
    相关资源
    最近更新 更多