Java 为什么wait()必须始终处于同步块中

Java 为什么wait()必须始终处于同步块中,java,multithreading,concurrency,wait,Java,Multithreading,Concurrency,Wait,我们都知道,为了调用,这个调用必须放在synchronized块中,否则就会抛出一个。但是做出此限制的原因是什么?我知道wait()会释放监视器,但是为什么我们需要通过同步特定块来显式获取监视器,然后通过调用wait()来释放监视器 如果可以在同步块外部调用wait(),保留其语义-挂起调用线程,那么潜在的损害是什么?只有在同时存在notify()时,才有意义,因此线程之间的通信总是相关的,这需要同步才能正常工作。有人可能会说这应该是隐含的,但这并没有真正的帮助,原因如下: 从语义上讲,您永远不

我们都知道,为了调用,这个调用必须放在synchronized块中,否则就会抛出一个。但是做出此限制的原因是什么?我知道
wait()
会释放监视器,但是为什么我们需要通过同步特定块来显式获取监视器,然后通过调用
wait()
来释放监视器

如果可以在同步块外部调用
wait()
,保留其语义-挂起调用线程,那么潜在的损害是什么?

只有在同时存在
notify()
时,才有意义,因此线程之间的通信总是相关的,这需要同步才能正常工作。有人可能会说这应该是隐含的,但这并没有真正的帮助,原因如下:

从语义上讲,您永远不会只是
wait()
。你需要满足一些条件,如果不满足,你就等它满足。所以你真正做的是

if(!condition){
    wait();
}
但是该条件是由一个单独的线程设置的,因此为了使其正常工作,您需要同步

还有一些问题,仅仅因为你的线程停止等待并不意味着你所寻找的条件是正确的:

  • 您可以得到虚假的唤醒(意味着线程可以在没有收到通知的情况下从等待中唤醒),或者

  • 可以设置条件,但第三个线程在等待的线程唤醒(并重新获取监视器)时再次使条件为false

要处理这些情况,您真正需要的始终是这方面的一些变化:

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}
更好的是,不要弄乱同步原语,而是使用
java.util.concurrent
包中提供的抽象

如果可以在同步块外部调用
wait()
,保留其语义-挂起调用线程,那么潜在的损害是什么?

让我们用一个具体示例来说明如果可以在同步块之外调用
wait()
,我们会遇到什么问题

假设我们要实现一个阻塞队列(我知道,API中已经有一个了:)

第一次尝试(没有同步)可以看到下面的内容

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}
类阻塞队列{
队列缓冲区=新的LinkedList();
public void give(字符串数据){
buffer.add(数据);
notify();//因为可能有人在等待!
}
公共字符串take()引发InterruptedException{
while(buffer.isEmpty())//由于虚假唤醒,请不要使用“if”。
等待();
return buffer.remove();
}
}
这就是可能发生的情况:

  • 使用者线程调用
    take()
    ,并看到
    缓冲区.isEmpty()

  • 在使用者线程继续调用
    wait()
    之前,生产者线程出现并调用完整的
    give()
    ,即
    buffer.add(数据);通知()

  • 使用者线程现在将调用
    wait()
    (并错过刚才调用的
    notify()

  • 如果不走运,生产者线程将不会产生更多的
    give()
    ,这是因为消费者线程从未唤醒,我们有一个死锁

  • 一旦您了解了这个问题,解决方案显而易见:使用
    synchronized
    确保在
    isEmpty
    wait
    之间从未调用
    notify

    无需详细说明:此同步问题是普遍存在的。正如MichaelBorgwardt指出的,wait/notify完全是关于线程之间的通信,因此您总是会遇到类似于上面描述的竞争条件。这就是为什么强制执行“仅在同步内等待”规则的原因


    《圣经》中的一段话很好地概括了这一点:

    您需要绝对保证服务员和通知者同意谓词的状态。服务员在谓词进入睡眠前的某个时间点检查谓词的状态,但其正确性取决于谓词进入睡眠时是否为true。这两个事件之间存在一段时间的漏洞,可能会破坏程序

    生产者和消费者需要达成一致的谓词在上面的示例
    buffer.isEmpty()
    中。通过确保在
    synchronized
    块中执行等待和通知来解决该协议


    这篇文章在这里被改写为一篇文章:

    直接来自java oracle教程:

    当线程调用d.wait时,它必须拥有d.wait的内在锁- 否则将抛出一个错误。在同步进程内调用wait 方法是获取内在锁的简单方法


    @滚球是对的。调用
    wait()
    ,这样线程就可以等待某种情况发生。当调用
    wait()
    时,线程将被迫放弃其锁。
    要放弃某些东西,你首先需要拥有它。线程首先需要拥有锁。 因此需要在
    synchronized
    方法/块内调用它

    是的,如果您没有检查
    synchronized
    method/block中的条件,我同意上述所有关于潜在损害/不一致的回答。然而,正如@shrini1000所指出的,仅仅在synchronized块中调用
    wait()
    ,并不能避免这种不一致性的发生


    这基本上与硬件架构有关(即RAM缓存

    如果您不将
    synchronized
    wait()
    notify()
    一起使用,则另一个线程可能会进入相同的blo
    class A {
    
        private Object X;
    
        makeChangeOnX(){
            while (! x.getCondition()){
                wait();
                }
            // Do the change
        }
    
        setConditionToTrue(){
            x.condition = true; 
            notifyAll();
    
        }
        setConditionToFalse(){
            x.condition = false;
            notifyAll();
        }
        bool getCondition(){
            return x.condition;
        }
    }
    
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
    
    synchronized(monitor) {
        boolean wasNotified = false;
        while(!wasNotified) {
            wait();
        }
    }
    
    class A {
       int a = 0;
      //something......
      public void add() {
       synchronization(this) {
          //this is your monitoring object and thread has to wait to gain lock on **this**
           }
      }