【问题标题】:Java, wait and notifyAll: guard against spurious wakeupsJava、wait 和 notifyAll:防止虚假唤醒
【发布时间】:2016-01-30 23:49:34
【问题描述】:

我有几个线程在做一些工作,然后必须进入睡眠/等待一个不确定的时间。稍后,他们都需要被唤醒并继续工作。我可以通过在一个对象上调用wait(),然后在需要恢复时在同一个对象上调用notifyall() 来做到这一点。在研究这个问题时,我发现了这个教程:http://tutorials.jenkov.com/java-concurrency/thread-signaling.html 显然,通过将信号存储在信号类中并在 while 循环中检查信号成员变量来防止丢失信号和虚假唤醒是一种很好的做法。 这是教程中的代码示例:

public class MonitorObject{
}

public class MyWaitNotify3{

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

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

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

此代码有效,但我需要唤醒所有线程,而不仅仅是一个。如果我用myMonitorObject.notifyAll(); 替换myMonitorObject.notify();,那将无法正常工作,因为第一个恢复工作的线程会将wasSignalled 标志设置为false,并且所有其他线程将被困在while 循环中。

我进行了一些更改,使我能够唤醒所有线程:

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

public void doWait(){
    synchronized(myMonitorObject){
        while(!wasSignalled){
            try{
                myMonitorObject.wait();
            } catch(InterruptedException e){

            }
        }

    }
}

public void resetSignal() {
    wasSignalled = false;
}

public void doNotifyAll() {
    synchronized(myMonitorObject){
        wasSignalled = true;
        myMonitorObject.notifyAll();
    }
}

但这不是一个很好的解决方案,因为现在我不能只唤醒一个线程,我必须在doNotify之后重置信号,然后才能再次使用doWait。

有没有人可以让我在等待的线程上同时使用notifynotifyAll 的解决方案?

关于示例的一件事我不明白,为什么我必须使用单独的 MonitorObject 类?为什么我不能在 MyWaitNotify 类本身上调用 waitnotify

像这样:

public class WaitNotify {
    boolean wasSignalled = false;

    public void doWait(){
        synchronized(this){
            while(!wasSignalled){
                try{
                    wait();
                } catch(InterruptedException e){

                }
            }
        }
    }

    public void resetSignal() {
        wasSignalled = false;
    }

    public void doNotifyAll() {
        synchronized(this){
            wasSignalled = true;
            notifyAll();
        }
    }
}

这似乎有效,有什么理由我不应该这样做?

【问题讨论】:

    标签: java multithreading wait notify


    【解决方案1】:

    使用世代整数。当线程阻塞时,阻塞直到生成整数发生变化。在调用notifyAll之前,先增加世代整数。

    【讨论】:

    • 是的。 boolean 版本也是变相的一代整数。或者我们可以简单地翻转一个布尔值。
    • @bayu.io 那么如果在线程唤醒之前它被翻转两次会发生什么?线程将重新进入睡眠状态。
    • true:) 但是如果整数增加 2^32 倍呢? :) 开个玩笑。
    • 谢谢,你能详细说明一下吗,一个小代码示例?
    • @BruceB 如果您更具体地了解您不了解的内容,这将有所帮助。
    【解决方案2】:

    Phaser 是用于此类用例的一个很好的高级工具。

        final Phaser phaser = new Phaser(1);
    
        doNotify()
            phaser.arrive();  // increase phase
    
        doWait()
            int phase = phaser.getPhase();
            phaser.awaitAdvance( phase );   // await phase change
    

    synchronized 在私有对象上具有其他人无法对其进行synchronized 的优势。如果您使用synchronized(this),则其他人可能也想使用this 作为锁定对象。

    【讨论】:

    • 谢谢,实际上 CountDownLatch 对我的目的来说已经足够了,但如果没有你的回答,我就不会找到它。
    猜你喜欢
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-16
    • 1970-01-01
    • 2018-07-16
    • 1970-01-01
    相关资源
    最近更新 更多