Java监视器:如何知道等待(长时间超时)是否因超时或Notify()而结束?

时间:2011-05-24 17:19:57

标签: java multithreading wait notify

首先,这是一个近乎重复的: 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很容易被捕获但是......

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

具体来说,有人可以使用一个示例使用ThreadLocal布尔值,该布尔值仅在notify()上设置为true,并且所有这些都在现有循环中,如下所示? (这或多或少是另一个线程中接受的答案,但没有给出具体的代码示例。我不熟悉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();
}

布尔“空”用于我在此问的具体问题之外的目的。我相信我需要添加另一个布尔值来满足原始问题的建议答案。如何将提议的解决方案集成到上面的现有代码片段中?感谢。

4 个答案:

答案 0 :(得分:8)

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

即便如此,你必须警惕虚假的唤醒(与signal的呼叫无法区分。)

答案 1 :(得分:3)

你应该使用当前的循环,无论是否知道wait是否超时 - 部分是由于虚假唤醒的可能性。但是,我完全不确定真的需要知道是否因为通知而退出了呼叫。

考虑通知在超时之前纳秒发生的情况与超时后纳秒发生通知的情况。这两者之间有用的区别是什么?从根本上说,如果两者发生在“大致相同的时间”,就会出现竞争条件。

据我所知,wait()确实没有让您判断通话是否超时,但不应影响你的代码。您应该循环并测试其他通知副作用的其他内容。

我不清楚ThreadLocal在哪个方面会发挥作用 - 如果你需要能够从等待的线程中分辨出来,这就是你想要的相反是否通知线程已达到某一点。我认为你根本不需要额外的变量 - 你的empty很好。

答案 2 :(得分:1)

使用内置监视器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 :(得分: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();
         }
     }
 }