【问题标题】:How to differentiate when wait(long timeout) exit for notify or timeout?如何区分等待(长时间超时)退出通知或超时?
【发布时间】:2011-03-24 18:53:10
【问题描述】:

有这个等待声明:

public final native void wait(long timeout) throws InterruptedException;

它可能会因 InterruptedException 退出,也可能因超时而退出,也可能因为 Notify/NotifyAll 方法在另一个线程中被调用,Exception 很容易捕获,但是......

有什么方法可以知道退出原因是超时还是通知?

编辑:

这是一种可行的棘手方法,(尽管我不喜欢它)

          long tBefore=System.currentTimeMillis();
          wait(TIMEOUT);
          if ((System.currentTimeMillis() - tBefore) > TIMEOUT) 
            { 
               //timeout
            }

【问题讨论】:

  • 我也有类似的需求,在我的情况下,Semaphore 更适合;如果超时已过,Semaphore.tryAcquire(long timeout, TimeUnit unit) 返回false
  • Semaphore 可以工作,除非您需要有等待的能力,这不受 Semaphore 公共合约的支持。
  • 需要将条件改为(>=):if ((System.currentTimeMillis() - tBefore) >= TIMEOUT)
  • 您的解决方案不正确。在通知事件之后,调度程序会在其他具有更高优先级的线程没有运行时恢复线程。这意味着,在通知之后,在线程恢复之前会出现一个时间间隔,如果它足以满足您的条件,您的代码会管理超时而不是正确的行为。

标签: java multithreading wait synchronized notify


【解决方案1】:

notify 可以返回的另一个原因是:虚假唤醒。这是不太可能但可能发生的事情,因为在某些硬件/操作系统组合上防止虚假唤醒非常昂贵。

因此,您始终必须在循环中调用 wait() 并重新检查您正在等待的条件。在这项工作期间,很容易同时检查超时。

有关详细信息,我推荐《Java 并发实践》一书。并使用更高级别的结构来为您解决所有问题。

【讨论】:

    【解决方案2】:

    除非您提供一些额外的代码,否则您无法区分这两者。例如,通过添加一个 ThreadLocal Boolean 仅在 notify() 上设置为 true

    但首先你必须确保你的逻辑需要这种差异化。

    【讨论】:

    • OP 已经知道不会抛出异常,并且想要区分超时和通知。这并没有为此提供任何有用的信息。
    • 我看不出在执行 notify() 时将 thread-local 布尔值设置为 true 有何帮助。调用 notify() 的线程不会是从 wait() 唤醒的线程。
    • 你的意思是简单的boolean 字段,我想。不是ThreadLocal。但简单的布尔字段可能会有所帮助。它甚至不需要是volatile,因为无论如何都会从synchronized 块调用wait/notify。只需在notify() 之前将其设置为true 并在wait() 之后检查/重置它。
    【解决方案3】:

    这并不能完全回答问题,但它可能会解决您的问题:使用更高级别的并发机制。等待/通知的级别通常比您想要的要低,这也是许多其他原因。

    例如,如果您使用的是BlockingQueue.poll(long, TimeUnit),您可以检查结果是否为 null 以了解您是否超时。

    【讨论】:

      【解决方案4】:

      不要使用System.currentTimeMillis(),而是使用System.nanoTime()

      第一个测量绝对时间(基于系统时钟),如果系统时间更改,可能会产生奇怪的结果。例如:如果时钟向后移动一个小时,则 5 秒的等待可能会持续一个小时,如果时钟向前移动,则会在 0 秒后等待 10 分钟。

      第二个测量相对时间。它总是以恒定的速度向一个方向运行,但它没有原点。这意味着这些值只能用于测量相对时间,但可以也不应该用于确定日期。

      【讨论】:

      • +1 鼓励人们停止编写因系统时钟更新而中断的代码。但是您没有提到应该始终检查等待的原因是否仍然有效。
      【解决方案5】:

      无法直接判断 - 也就是说,您必须添加额外的代码来确定这一点。通常,当您等待()时,您正在等待以某种方式改变对象状态的事情发生 - 例如。通过设置一个布尔变量,也许。如果是这种情况,那么您可以简单地检查该变量的状态以查看事件是否发生,或者您只是超时。或者您可以查看 System.currentTimeMillis() 的值以查看经过的时间是否大于或等于超时期限 - 如果是,那将是您可能超时的线索(尽管这不是绝对保证)。或者如果经过的时间小于超时时间,那么你肯定没有超时。这有帮助吗?

      【讨论】:

      • @Hernán Eche 正如我在回答中所说,这不是绝对的保证。如果你能说出,你为什么要识别它是否超时?我们可以寻找一些解决方案。
      【解决方案6】:

      您应该使用不等待/通知的方法。

      使用带条件的锁会更好https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html#await-long-java.util.concurrent.TimeUnit-

      它具有等待超时,如果在从方法返回之前可检测到等待时间过去,则返回 false,否则返回 true

      【讨论】:

        【解决方案7】:

        在通知和超时时不会抛出异常。

        我认为最好依赖java.lang.concurrent包同步对象而不是使用Object.wait()

        【讨论】:

          猜你喜欢
          • 2013-03-12
          • 2011-09-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-22
          • 1970-01-01
          • 2012-10-14
          • 2012-02-07
          • 2020-08-15
          相关资源
          最近更新 更多