【问题标题】:Java Monitors: How to know if wait(long timeout) ended by timeout or by Notify()?Java Monitors:如何知道等待(长时间超时)是否以超时或 Notify()结束?
【发布时间】:2011-09-01 03:24:00
【问题描述】:

首先,这是几乎重复的: How to differentiate when wait(long timeout) exit for notify or timeout?

但这是一个新的后续问题。

有这个等待声明:

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

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

我的代码绝对需要知道退出是来自超时还是通知。 (以后需要重新设计这段代码,但现在做不到。所以我需要知道退出等待的原因。)

具体来说,有人可以给出一个 example 使用仅在 notify() 上设置为 true 的 ThreadLocal 布尔值,并且所有这些都在现有循环中,如下所示? (这或多或少是另一个线程中接受的答案,但没有给出具体的代码示例。我对 Java 不是很熟悉,所以我需要一个具体的代码示例——最好是在下面现有代码的上下文中.)

public synchronized int getLastSequenceNumber() {
    while (empty) {
        try {
            wait(waitTimeValue);
        } catch (InterruptedException e) {}
    }
    empty = true;
    return reportedSequenceNumber;
}
public synchronized void reconcileLastSequenceNumber(int sequenceNumber) {
    empty = false;
    this.reportedSequenceNumber = sequenceNumber;
    notifyAll();
}

布尔“空”的用途超出了我在这里提出的具体问题。我相信我需要添加另一个布尔值来满足原始问题的建议答案。我如何将该提议的解决方案集成到上面的现有代码 sn-p 中?谢谢。

【问题讨论】:

  • 您要求队列类似的行为。检查 java.util.concurrent.BlockingQueue 及其任何实现。

标签: java multithreading wait notify


【解决方案1】:

这是基于 Jenkov 的信号类的扩展版本。如果它不以Notify 结尾,则会引发异常。当我遇到同样的问题时,认为它可能会有所帮助。

public class MonitorObject{
 }

 public class Signal{

     MonitorObject myMonitorObject = new MonitorObject();
     boolean wasSignalled = false;

     public void doWait(int timeOut) throws InterruptedException,TimeoutException{
         synchronized(myMonitorObject){
             long startTime = System.currentTimeMillis();
             long endTime = startTime + timeOut;
             Log.d(TAG, String.format("MonitorStart time %d",startTime));

             while(!wasSignalled){
                 long waitTime = endTime - System.currentTimeMillis();
                 if(waitTime > 0) 
                     myMonitorObject.wait(waitTime);        
                 else{
                     Log.e(TAG, String.format("Monitor Exit timeout error"));
                     throw new TimeoutException();
                 }       
             }

             Log.d(TAG, String.format("MonitorLoop Exit currentTime=%d EndTime=%d",System.currentTimeMillis(),startTime + timeOut));
             //Spurious signal so clear signal and continue running.
             wasSignalled = false;
         }
     }

     public void doNotify(){
         synchronized(myMonitorObject){
             wasSignalled = true;
             myMonitorObject.notify();
         }
     }
 } 

【讨论】:

    【解决方案2】:

    使用内置监视器 API 无法直接报告此问题,但您可以将 wait() 和其他函数替换为显式跟踪此问题的新实现(未经测试):

    private int wait_ct = 0, signal_ct = 0;
    
    public void checkedNotifyAll() {
      synchronized {
        signal_ct = wait_ct;
        notifyAll();
      }
    }
    
    public void checkedNotify() {
      synchronized {
        signal_ct++;
        if (signal_ct > wait_ct)
          signal_ct = wait_ct;
        notify();
    }
    
    // Returns true if awoken via notify
    public boolean waitChecked(long timeout, int nanos) throws InterruptedException {
      synchronized(this) {
        try {
          wait_ct++;
          super.wait(timeout, nanos);
          if (signal_ct > 0) {
            signal_ct--;
            return true;
          }
          return false;
        } finally {
          wait_ct--;
          if (signal_ct > wait_ct) signal_ct = wait_ct;
          notify(); // in case we picked up the notify but also were interrupted
        }
    }
    
    // Note: Do not combine this with normal wait()s and notify()s; if they pick up the signal themselves
    // the signal_ct will remain signalled even though the checkedWait()s haven't been
    // awoken, potentially resulting in incorrect results in the event of a spurious wakeup
    

    当然,这不一定是一个好方法;如果您在调用notify() 之前超时,毕竟信号条件可能会丢失。你真的应该在一个循环中等待,检查一些持久条件。

    【讨论】:

      【解决方案3】:

      您最好使用Condition(及其await 方法)而不是内置监视器,因为await 返回一个boolean 值,指示等待是否超时。

      即便如此,您也必须提防虚假唤醒(这与对 signal 的调用无法区分。)

      【讨论】:

      • 看起来很有趣。我现在正在阅读 Condition 上的 Javadoc。当我读完,如果觉得合适,我会试试看。看来我需要一些时间才能弄清楚...
      【解决方案4】:

      无论wait 是否超时,您都应该像现在一样使用循环 - 部分原因是可能会出现虚假唤醒。但是,我完全不确定您是否真的需要知道呼叫是否由于通知而退出。

      考虑通知发生在超时前一纳秒的情况与通知发生在超时后一纳秒的情况。两者之间有用的区别是什么?如果两者“大约同时”发生,则基本上存在竞争条件。

      据我所知,wait() 确实不会让您判断调用是否超时,但它不应该影响您的代码.无论如何,您应该循环并测试其他作为通知副作用的东西。

      老实说,我不清楚 ThreadLocal 会在哪里发挥作用 - 如果您需要能够从等待线程中判断通知线程已达到某一点。我认为您根本不需要额外的变量 - 您的 empty 很好。

      【讨论】:

      • Jon,我非常尊重你,但我特别说“我的代码绝对需要知道退出是来自超时还是通知。”这不是时间问题。这是一个逻辑问题,无论多么小的时间可以将原因分开,它都是正确的。
      • @MoutainX:是的,你声称你需要知道它。但我不相信。如果超时发生另一个线程在reconcileLastSequenceNumber 但在notify() 调用之前会发生什么?这真正与在notify() 调用之后 立即发生的超时不同吗?有什么不同?如果您可以解释为什么您认为差异至关重要,那可能会提出另一种方式 - 但我怀疑在证明您最初的信念的同时,您会发现您已经在其他地方获得了所需的信息(例如由于empty)?
      猜你喜欢
      • 2011-03-24
      • 1970-01-01
      • 2021-05-03
      • 2012-10-14
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多