[英]why wait/notify/notifyAll methods are not synchronized in java ?
在 Java 中,每当我们需要调用 wait/notify/notifyAll 时,我们需要访问 object 监视器(通过同步方法或通过同步块)。 所以我的问题是为什么 java 没有 go 同步等待/通知方法消除了从同步块或方法调用这些方法的限制。
如果这些被声明为同步,它将自动获取监视器访问权限。
对于 notify 和 notifyAll,您的想法的问题在于,当您通知您时,您通常还会在同一个同步块中执行其他操作。 因此,使通知方法同步不会给您带来任何好处,您仍然需要该块。 同样,等待必须在同步块或方法中才能有用,例如在自旋锁中,无论如何测试都必须同步。 因此,对于您的建议,锁定的粒度都是错误的。
这是一个示例,这是关于 Java 中最简单的队列实现:
public class MyQueue<T> {
private List<T> list = new ArrayList<T>();
public T take() throws InterruptedException {
synchronized(list) {
while (list.size() == 0) {
list.wait();
}
return list.remove(0);
}
}
public void put(T object) {
synchronized(list) {
list.add(object);
list.notify();
}
}
}
因此,您可以拥有将内容添加到队列的生产者线程和取出内容的消费者线程。 当一个线程从队列中取出某些东西时,它需要在同步块中检查列表中是否存在某些内容,一旦收到通知,它需要重新获取锁并确保列表中仍然存在某些内容(因为一些其他消费者线程可能已经介入并抓住了它)。还有“虚假唤醒”现象:您不能依靠被唤醒作为发生某事的充分证据,您需要检查您正在等待的任何条件for 实际上是正确的,这需要在同步块中完成。
在这两种情况下,围绕等待的检查需要在持有锁的情况下进行,这样当代码根据这些检查采取行动时,它就知道这些结果当前是有效的。
好问题。 我认为JDK7 Object 实现中的评论对此有所了解(强调我的):
此方法导致当前线程(称为
T
)将自己置于此 object 的等待集中,然后放弃此 object 上的任何和所有同步声明。...
然后线程
T
以这种通常的方式从等待集中移除,与其他线程一起获得在 object 上同步的权利; 一旦它获得了 object 的控制权,它对 object 的所有同步声明都将恢复到之前的状态 - 即,恢复到调用wait
方法时的状态。 线程T
然后从调用wait
方法返回。 因此,在从wait
方法返回时,object 和线程T
的同步 state 与调用wait
方法时完全相同。
所以我认为要注意的第一点是wait()
直到调用者完成等待(显然)才返回。 这意味着如果wait()
本身是同步的,那么调用者将继续持有 object 上的锁,并且没有其他人能够wait()
或notify()
。
现在显然wait()
在幕后做了一些棘手的事情,以迫使调用者失去其对 object 的锁定的所有权,但如果wait()
本身是同步的。
第二点是,如果多个线程在 object 上等待,当使用notify()
唤醒其中一个时,标准的争用方法用于仅允许一个线程在 object 上同步,并且假设wait()
将调用者的同步声明恢复到调用wait()
之前的确切 state 。 在我看来,要求调用者在调用wait()
之前持有锁可以简化这一点,因为它不需要检查调用者是否应该在wait()
返回后继续持有锁。 合约规定调用者必须继续持有锁,因此简化了一些实现。
或者也许只是为了避免出现“如果wait()
和notify()
都同步,并且wait()
直到notify()
被调用时才返回,那么如何成功使用两者”的逻辑悖论的出现?”。
无论如何,这些是我的想法。
在我读过和写过的所有非错误代码中,它们都在更大的同步块中使用wait/notify
,涉及读/写其他条件
synchronized(lock)
update condition
lock.notify()
synchronized(lock)
while( condition not met)
lock.wait()
如果wait/notify
本身是synchronized
的,则不会对所有正确的代码造成损害(可能会造成小的性能损失); 它对所有正确的代码也没有任何好处。
但是,它会允许并鼓励更多不正确的代码。
对多线程更有经验的人应该可以随意介入,但我相信这会消除同步块的多功能性。 使用它们的目的是在特定的 object 上进行同步,该 object 用作受监控的资源/信号量。 然后使用等待/通知方法来控制同步块内的执行流程。
请注意,同步方法是在方法持续时间内this
进行同步的简写(或 class 用于 static 方法)。 同步等待/通知方法本身将消除它们作为线程之间停止/开始信号的使用点。
同步的等待通知 model 要求您先获取 object 上的监视器,然后再继续执行任何工作。 它与同步块使用的互斥 model 不同。
等待通知或相互协作 model 通常用于生产者-消费者场景,其中一个线程产生由另一个线程消费的事件。 编写良好的实现将努力避免消费者挨饿或生产者用太多事件超出消费者的情况。 为避免这种情况,您将使用等待通知协议,其中
wait
生产者产生事件。notifies
消费者,然后通常会进入睡眠状态,直到消费者notified
它。notifies
生产者它已完成对事件的处理。 在这种情况下,您可能有许多生产者和消费者。 通过互斥 model 获取监视器,在wait
、 notify
或notifyAll
时必然会销毁此 model,因为生产者和消费者没有显式执行等待。 底层线程将出现在监视器的等待集(由等待通知模型使用)或条目集(由互斥模型使用)中。 调用notify
或notifyAll
信号线程从等待集移动到监视器的条目集(其中可能存在多个线程之间的监视器争用,而不仅仅是最近通知的线程)。
现在,当您想使用互斥 model 在wait
、 notify
和notifyAll
上自动获取监视器时,通常表明您不需要使用 wait-notify model。 这是通过推理得出的——您通常只会在一个线程中完成一些工作之后,即在 state 发生更改后,才向其他线程发出信号。 如果您自动获取监视器并调用notify
或notifyAll
,您只是将线程从等待集移动到条目集,程序中没有任何中间 state,这意味着过渡是不必要的。 很明显,JVM 的作者意识到了这一点,并没有将这些方法声明为同步。
您可以在 Bill Venner 的书 - Inside the Java Virtual Machine中阅读有关监视器的等待集和入口集的更多信息。
我认为没有synchronized
的wait
在某些情况下可以很好地工作。 但它不能用于没有竞争条件的复杂场景,可能会出现“虚假唤醒”。
代码适用于队列。
// producer
give(element){
list.add(element)
lock.notify()
}
// consumer
take(){
obj = null;
while(obj == null)
lock.wait()
obj = list.remove(0) // ignore error indexoutofrange
return obj
}
这段代码没有解释共享数据的 state,它会忽略最后一个元素,在多线程条件下可能不起作用。 如果没有竞争条件,则1
、 2
中的列表可能具有完全不同的状态。
// consumer
take(){
while(list.isEmpty()) // 1
lock.wait()
return list.remove(0) // 2
}
现在,让它变得更加复杂和明显。
指令的执行
give(element) lock.notify()->take() lock.wait() resurrected->take() list.remove(0)->rollback(element)
give(element) lock.notify()->take() lock.wait() resurrected->rollback(element)->take() list.remove(0)
“虚假唤醒”发生,也使代码不可预测。
// producer
give(element){
list.add(element)
lock.notify()
}
rollback(element){
list.remove(element)
}
// business code
produce(element){
try{
give(element)
}catch(Exception e){
rollback(element) // or happen in another thread
}
}
// consumer
take(){
obj = null;
while(obj == null)
lock.wait()
obj = list.remove(0) // ignore error indexoutofrange
return obj
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.