我们都知道为了调用Object.wait(),这个调用必须放在同步块中,否则抛出IllegalMonitorStateException。但是为什么要做出这样的限制呢?我知道wait()释放监视器,但为什么我们需要通过使特定块同步显式获取监视器,然后通过调用wait()释放监视器?

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


当前回答

如果在wait()之前没有同步,可能会导致如下问题:

If the 1st thread goes into makeChangeOnX() and checks the while condition, and it is true (x.metCondition() returns false, means x.condition is false) so it will get inside it. Then just before the wait() method, another thread goes to setConditionToTrue() and sets the x.condition to true and notifyAll(). Then only after that, the 1st thread will enter his wait() method (not affected by the notifyAll() that happened few moments before). In this case, the 1st thread will stay waiting for another thread to perform setConditionToTrue(), but that might not happen again.

但是如果你把synchronized放在改变对象状态的方法之前,这就不会发生。

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;
    }
}

其他回答

如果可以在同步块之外调用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();
    }
}

这是可能发生的事情:

消费者线程调用take()并看到buffer.isEmpty()。 在消费者线程继续调用wait()之前,一个生产者线程出现并调用一个完整的give(),即buffer.add(data);notify (); 消费者线程现在将调用wait()(并错过刚刚调用的notify())。 如果不走运,生产者线程将不会产生更多的give(),因为消费者线程从未被唤醒,我们有一个死锁。

一旦您理解了这个问题,解决方案是显而易见的:使用synchronized来确保在isEmpty和wait之间永远不会调用notify。

不详细说明:这个同步问题是普遍存在的。正如Michael Borgwardt所指出的,wait/notify完全是关于线程之间的通信,所以您最终总是会得到类似于上面描述的竞争条件。这就是为什么强制执行“只在synchronized内部等待”规则的原因。


@Willie发布的一段链接很好地总结了这一点:

您需要绝对保证侍者和通知者就谓词的状态达成一致。服务员在稍微在进入睡眠之前的某个时间点检查谓词的状态,但它的正确性取决于谓词在进入睡眠时为真。在这两个事件之间有一段时间的漏洞,这可能会破坏程序。

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


这篇文章已经被改写为一篇文章:Java:为什么等待必须在同步块中调用

Thread wait on the monitoring object (object used by synchronization block), There can be n number of monitoring object in whole journey of a single thread. If Thread wait outside the synchronization block then there is no monitoring object and also other thread notify to access for the monitoring object, so how would the thread outside the synchronization block would know that it has been notified. This is also one of the reason that wait(), notify() and notifyAll() are in object class rather than thread class.

基本上监视对象在这里是所有线程的公共资源,监视对象只能在同步块中可用。

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**
       }
  }

@Rollerball是对的。调用wait(),以便线程可以等待某些条件的发生,当wait()调用发生时,线程被迫放弃它的锁。 要放弃一些东西,你需要先拥有它。线程首先需要拥有锁。 因此需要在同步方法/块中调用它。

是的,我同意上面所有关于潜在损害/不一致的答案,如果你没有检查同步方法/块中的条件。然而,正如@shrini1000所指出的,只是在同步块中调用wait()并不能避免这种不一致的发生。

这是一篇不错的文章。

我们都知道wait(), notify()和notifyAll()方法用于线程间 通信。为摆脱误信号和伪唤醒问题,等待线程 总是等待一些条件。 例如,

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

然后通知线程集wasNotified变量为true和notify。

每个线程都有自己的本地缓存,所以所有的更改都先写在那里 然后逐步提升到主存。

是否这些方法没有在同步块中调用,wasNotified变量 不会被刷新到主内存中,而是在线程的本地缓存中 因此,等待线程将继续等待信号,尽管它已通过通知重置 线程。

为了修复这些类型的问题,这些方法总是在同步块中调用 这确保当同步块开始时,那么一切将从主读取 内存,并将在退出同步块之前被刷新到主内存。

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}

谢谢,希望它能澄清。

直接从这个Java oracle教程:

当线程调用d.wait时,它必须拥有d -的内在锁 否则抛出错误。在synchronized中调用等待 方法是一种获取内在锁的简单方法。