为什么 wait() 必须始终在同步块中
我们都知道,为了调用 Object.wait()
,这个调用必须放在同步块中,否则就会抛出一个 IllegalMonitorStateException
。但是,进行此限制的原因是什么?我知道 释放监视器,但是为什么我们需要通过使特定块同步来显式获取监视器,然后通过调用来释放监视器?wait()
wait()
如果可以在同步块外部调用,保留其语义 - 挂起调用方线程,则潜在的损害是什么?wait()
我们都知道,为了调用 Object.wait()
,这个调用必须放在同步块中,否则就会抛出一个 IllegalMonitorStateException
。但是,进行此限制的原因是什么?我知道 释放监视器,但是为什么我们需要通过使特定块同步来显式获取监视器,然后通过调用来释放监视器?wait()
wait()
如果可以在同步块外部调用,保留其语义 - 挂起调用方线程,则潜在的损害是什么?wait()
如果可以在同步块外部调用
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
notify
isEmpty
wait
不谙述:此同步问题是普遍存在的。正如Michael Borgwardt所指出的,等待/通知完全是关于线程之间的通信,所以你总是会得到一个类似于上面描述的竞争条件。这就是强制执行“仅在同步内部等待”规则的原因。
@Willie发布的链接中的一段话很好地总结了这一点:
您需要绝对保证服务员和通知者同意谓词的状态。服务员在谓词进入睡眠状态之前的某个时间点检查谓词的状态,但它取决于谓词在进入睡眠状态时是否为 true 的正确性。这两个事件之间存在一段时间的漏洞,这可能会破坏程序。
生产者和消费者需要商定的谓词在上面的例子中。协议通过确保以块为单位执行等待和通知来解决。buffer.isEmpty()
synchronized
这篇文章已经被重写为一篇文章:Java:为什么必须在同步块中调用等待
只有当还有 一个 时才有意义,所以它总是关于线程之间的通信,并且需要同步才能正常工作。有人可能会争辩说这应该是隐含的,但这并没有真正的帮助,原因如下:wait()
notify()
从语义上讲,你永远不会只是.你需要一些条件才能满足,如果不是,你就等到它满足。所以你真正做的是wait()
if(!condition){
wait();
}
但是条件是由单独的线程设置的,因此为了正确完成此工作,您需要同步。
还有几处错误,仅仅因为你的线程退出等待并不意味着你正在寻找的条件是真实的:
您可能会获得虚假唤醒(这意味着线程可以在没有收到通知的情况下从等待中唤醒),或者
可以设置条件,但当等待线程唤醒(并重新获取监视器)时,第三个线程会再次使条件变为 false。
为了处理这些情况,您真正需要的始终是以下情况的一些变体:
synchronized(lock){
while(!condition){
lock.wait();
}
}
更好的是,不要弄乱同步基元,而是使用包中提供的抽象。java.util.concurrent