Java线程自动唤醒

Java线程自动唤醒,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我有一个Java线程,它执行如下操作: while (running) { synchronized (lock) { if (nextVal == null) { try { lock.wait(); } catch (InterruptedException ie) { continue; } } v

我有一个Java线程,它执行如下操作:

while (running) {
    synchronized (lock) {
        if (nextVal == null) {
            try {
                lock.wait();
            } catch (InterruptedException ie) {
                continue;
            }
        }

        val = nextVal;
        nextVal = null;
    }
    ...do stuff with 'val'...
}
if (val == null) {
    LOG.error("null value");
} else {
    synchronized (lock) {
        nextVal = newVal;
        lock.notify();
    }
}
在其他地方,我设置如下值:

while (running) {
    synchronized (lock) {
        if (nextVal == null) {
            try {
                lock.wait();
            } catch (InterruptedException ie) {
                continue;
            }
        }

        val = nextVal;
        nextVal = null;
    }
    ...do stuff with 'val'...
}
if (val == null) {
    LOG.error("null value");
} else {
    synchronized (lock) {
        nextVal = newVal;
        lock.notify();
    }
}
偶尔(实际上每几百万次一次)nextVal会被设置为null。我已经输入了日志消息,我可以看到执行顺序如下所示:

while (running) {
    synchronized (lock) {
        if (nextVal == null) {
            try {
                lock.wait();
            } catch (InterruptedException ie) {
                continue;
            }
        }

        val = nextVal;
        nextVal = null;
    }
    ...do stuff with 'val'...
}
if (val == null) {
    LOG.error("null value");
} else {
    synchronized (lock) {
        nextVal = newVal;
        lock.notify();
    }
}
  • thread1将nextVal设置为newVal
  • thread1调用lock.notify()
  • 线程2从锁中唤醒。等待()
  • thread2将val设置为nextVal
  • thread2将nextVal设置为null
  • thread2与val有关
  • thread2调用lock.wait()
  • 线程2从锁中唤醒。等待()
    • 没有其他线程调用lock.notify(),thread2也没有被中断
  • thread2将val设置为nextVal(为null)
  • 等等
  • 我已经明确检查过了,锁第二次被唤醒,没有被中断


    我在这里做错什么了吗?

    是的,
    线程
    会自动醒来。这一点在以下文件中明确说明:

    线程也可以在不被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒

    您需要在循环中等待。javadoc中也明确提到了这一点:

     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }
    

    虚假唤醒非常常见,因此建议在循环中的某个条件下总是
    wait()

    按如下方式更改代码:

    while (nextVal == null) {
        try {
            lock.wait();
        } catch (InterruptedException ignored) {
        }
    }
    
    特定于您共享的代码:
    while
    还可以帮助您避免在代码点击
    continue时释放和重新获取相同锁的不必要的开销

    参考文献:

    public final void wait()
    文档:
    ... 中断和虚假唤醒是可能的,这种方法应始终在循环中使用:

    synchronized(obj){
    而()
    obj.wait();
    …//执行适合条件的操作
    }
    
    这可能是虚假的唤醒,但这不是唯一可能的原因。这绝对是你逻辑上的问题。您需要将等待放在重新测试条件的循环中

    当线程从等待中醒来时,它不再具有锁。它在开始等待时释放了锁,需要重新获取锁才能继续。由于线程相关性(这可能是代码大部分时间工作的原因),刚刚醒来的线程通常是下一个线程,但仍然有可能不是;另一个线程可能会进入并阻塞锁,在被唤醒的线程获取锁之前,完成它的工作并将nextVal保留为null。这意味着线程在等待之前进行的null测试不再相关。一旦你有了锁,你必须回去再次测试

    将代码更改为使用循环,如:

    synchronized(lock) {
        while (nextVal == null) {
            lock.wait();
        }
       ...
    

    这样,测试是在线程拥有锁的情况下进行的,while循环下面的块中发生的任何事情都可以确定nextVal真的不是null。

    +1对于一个写得很好的解释,但是-1对于没有更仔细地阅读Javadoc:-)IIRC,不能保证线程不会从等待中醒来,而等待条件仍然不满足。您必须始终检查条件,如果不满足,则重新执行等待。(原因与实现“唤醒”逻辑的复杂性有关。如果没有大量昂贵的同步,确保从不出现虚假唤醒几乎是不可能的。)哇,我做了很多线程,这是我第一次注意到这一点!感谢所有温和的提醒,重新阅读文档:-这是线程的一个特点,您可以运行无限次减去delta次,但仍然可能包含bug。我建议将其作为线程问题的最终文本。由于多线程固有的复杂性,正常的方法是无用的,您无法编写和测试;如上所述,完全测试并发代码几乎是不可能的-您必须事先知道常见的陷阱。我应该注意,'while(nextVal==null){'解决方案对我来说不起作用,因为我忽略了这个线程通过一个方法停止的细节:synchronized(lock){running=true;lock.notify();}所以我不能在'nextVal==null'上旋转。相反,我添加了:if(nextVal==null)continue;在'val=nextVal'之前,以确保在循环的每次行程中都检查'running'
    。在其他地方检查
    运行
    标志,然后返回。您甚至可以在中断中添加一些逻辑,然后
    中断
    线程停止。是的,中断是停止运行线程的正确方法。使用单独的
    布尔
    标志方法充满了陷阱。我希望
    正在运行的
    在此处标记为易失性!