小编典典
如果可以wait()在同步块之外调用并保留其语义-挂起调用者线程,可能造成什么损害?
让我们wait()用一个具体的例子来说明如果在同步块之外调用该函数会遇到什么问题。
假设我们要实现一个阻塞队列(我知道,API中已经有一个队列了:)
第一次尝试(没有同步)可能看起来像下面的样子
class BlockingQueue {
Queue buffer = new LinkedList();
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(),生产者线程会出现并调用full give(),即buffer.add(data); notify();
使用者线程现在将调用wait()(并且错过了notify()刚刚被调用的线程)。
如果运气不好,生产者线程将不会产生更多give()的结果,因为消费者线程永远不会醒来,而且我们陷入了僵局。
理解了问题之后,解决方案就显而易见了:用于synchronized确保notify在isEmpty和之间不调用wait。
无需赘述:同步问题是普遍的。正如Michael Borgwardt指出的那样,等待/通知完全是关于线程之间的通信的,所以你总是会遇到与上述情况类似的竞争状态。这就是为什么强制执行“仅在同步中等待”规则的原因。
@Willie发布的链接中的一段对其进行了很好的总结:
你需要绝对保证服务员和通知者就谓词的状态达成一致。服务员在进入睡眠之前的某个时候会稍稍检查谓词的状态,但是它的正确性取决于谓词在进入睡眠时是正确的。这两个事件之间存在一段时间的漏洞,这可能会破坏程序。
在上面的示例中,生产者和消费者需要达成共识的谓词buffer.isEmpty()。通过确保等待和通知在synchronized块中执行来解决协议。
2020-02-29