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

diy*_*diy 250 java concurrency multithreading wait

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

如果可以wait()在同步块之外调用,保留它的语义 - 暂停调用程序线程,可能造成的损害是什么?

aio*_*obe 274

如果可以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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是可能发生的事情:

  1. 消费者线程调用take()并看到了buffer.isEmpty().

  2. 在消费者线程继续调用之前wait(),生成器线程出现并调用一个完整的give(),即buffer.add(data); notify();

  3. 消费者线程现在将调用wait()(并且错过notify()刚刚调用的那个).

  4. 如果运气不好,生产者线程将不会产生更多give()因为消费者线程永远不会醒来的事实,并且我们有一个死锁.

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

没有详细说明:此同步问题是通用的.正如Michael Borgwardt指出的那样,wait/notify是关于线程之间的通信的,所以你总是会遇到与上述类似的竞争条件.这就是强制执行"仅在内部同步"规则的原因.


@Willie发布链接中的段落总结得很好:

您需要绝对保证服务员和通知者同意谓词的状态.服务员在进入睡眠状态之前稍微稍微检查一下谓词的状态,但这取决于谓词在进入睡眠状态时的正确性.这两个事件之间存在一段时间的漏洞,这可能会破坏该计划.

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


这篇文章在这里被重写为一篇文章:Java:为什么必须在synchronized块中调用wait


Mic*_*rdt 225

wait()只有在存在a 时才有意义notify(),所以它始终是线程之间的通信,并且需要同步才能正常工作.有人可能会认为这应该是隐含的,但由于以下原因,这不会有任何帮助:

在语义上,你永远不会wait().你需要一些条件来满足,如果不是,你就等到它.所以你真正做的是

if(!condition){
    wait();
}
Run Code Online (Sandbox Code Playgroud)

但是条件是由一个单独的线程设置的,所以为了使这个工作正常,你需要同步.

还有一些问题,只是因为你的线程退出等待并不意味着你正在寻找的条件是真的:

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

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

为了应对这些情况你真正需要的是永远的这样一些变化:

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}
Run Code Online (Sandbox Code Playgroud)

更好的是,不要混淆同步原语并使用java.util.concurrent包中提供的抽象.

  • 另一个讨厌的场景:条件是假的,我们即将进入wait()然后另一个线程更改条件并调用notify().因为我们还没有等待(),我们会错过这个notify().换句话说,测试和等待,以及更改和通知必须是*atomic*. (9认同)
  • 这里也有详细的讨论,说的基本相同.http://coding.derkeiler.com/Archive/Java/comp.lang.java.programmer/2006-01/msg01130.html (3认同)
  • 我仍然可以执行以下操作:while(!condition){synchronized(this){wait();}}这意味着即使在synchronized块中正确调用wait(),在检查条件和等待之间仍然存在竞争.那么这个限制背后是否还有其他原因,可能是因为它在Java中的实现方式? (2认同)

小智 12

@Rollerball是对的.将wait()被调用,从而使线程可以等待,当这种情况发生的一些情况wait()调用发生时,线程被迫放弃其锁.
要放弃一些东西,你需要先拥有它.线程首先需要拥有锁.因此需要在synchronized方法/块中调用它.

是的,如果您没有检查synchronized方法/块内的条件,我同意上述有关潜在损害/不一致的所有答案.然而,正如@ shrini1000指出的那样,只需wait()在synchronized块内调用就不会避免这种不一致的发生.

这是一个很好的阅读..

  • @Popeye'正确'解释'正确'.你的评论对任何人都没用. (4认同)