如果将这些声明为synchronized,它将自动获取监视器访问权限。
答案 0 :(得分:10)
对于notify和notifyAll,您的想法的问题是,当您通知您还有其他东西时,您通常在同一个同步块中进行操作。因此,使notify方法同步不会给你任何东西,你仍然需要块。同样,等待必须在同步块或方法中以便有用,例如在自旋锁内,无论如何必须同步测试。所以锁定的粒度对你的建议都是错误的。
这是一个例子,这是关于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();
}
}
}
因此,您可以使用生产者线程将事物添加到队列中,并使用消费者线程来解决问题。当一个线程从队列中获取某些内容时,它需要在synchronized块中检查列表中是否存在某些内容,并且一旦通知它,它需要重新获取锁定并确保列表中仍然存在某些内容(因为某些内容)其他消费者线程可能已介入并抓住它。)还有“虚假的唤醒”现象:你不能依赖被唤醒作为发生事情的充分证据,你需要检查你正在等待的任何条件for实际上是真的,需要在synchronized块中完成。
在这两种情况下,需要在保持锁定的情况下进行等待检查,以便当代码根据这些检查采取行动时,它知道这些结果当前是有效的。
答案 1 :(得分:7)
好问题。 JDK7 Object implementation中的评论对此有所启发,我认为(强调我的):
此方法导致当前线程(称为
T
)放置 本身在此对象的等待集中,然后放弃任何 以及此对象的所有同步声明。...
然后从等待集中删除线程
T
通常的方式与其他线程的权利同步 宾语;一旦它获得了对象的控制权,全部 对象的同步声明恢复到现状 赌注 - 也就是说wait
时的情况 方法被调用。 线程T
然后从中返回 调用wait
方法。因此,从...返回wait
方法,对象和线程的同步状态T
与wait
方法完全相同 调用
所以我认为要注意的第一点是wait()
在调用者完成等待之前不会返回(显然)。这意味着如果wait()
本身已同步,则调用者将继续保持对象的锁定,其他任何人都无法wait()
或notify()
。
现在显然wait()
在幕后做了一些棘手的事情来迫使调用者失去对对象锁的所有权,但也许这个技巧不会起作用(或者说工作要困难得多) )如果wait()
本身已同步。
第二点是,如果多个线程正在等待一个对象,当notify()
用于唤醒其中一个时,标准争用方法用于仅允许一个线程在对象上同步,并且wait()
应该将呼叫者的同步声明恢复为调用wait()
之前的确切状态。在我调用wait()
之前要求调用者保持锁定似乎可以简化这一点,因为它无需检查调用者是否应该在wait()
之后继续保持锁定回报。合同规定调用者必须继续持有锁,因此简化了一些实现。
或许这样做是为了避免出现“if wait()
和notify()
同步,wait()
直到notify()
才会出现逻辑悖论的情况。被称为,如何才能成功使用?“
无论如何,这些都是我的想法。
答案 2 :(得分:2)
我的猜测是需要synchronized
阻止的原因是使用wait()
或notify()
作为synchronized
块中的唯一操作几乎总是如此无论如何都是一个错误。
Findbugs甚至对此有警告,称之为“naked notify”。
答案 3 :(得分:2)
在我阅读和编写的所有非错误代码中,所有这些代码都在更大的同步块中使用wait/notify
,其中涉及读/写其他条件
synchronized(lock)
update condition
lock.notify()
synchronized(lock)
while( condition not met)
lock.wait()
如果wait/notify
本身synchronized
,则不会对所有正确的代码造成任何伤害(可能会造成轻微的性能损失);对所有正确的代码都没有任何好处。
然而,它会允许并鼓励更多错误的代码。
答案 4 :(得分:1)
对于多线程更有经验的人应该随时介入,但我相信这会消除同步块的多功能性。使用它们的目的是在作为受监视资源/信号量的特定对象上进行同步。然后使用wait / notify方法控制同步块中的执行流程。
请注意,同步方法是在方法持续时间(或静态方法的类)上在this
上同步的简写。同步等待/通知方法本身将删除它们在线程之间用作停止/运行信号的点。
答案 5 :(得分:1)
同步的wait-notify模型要求您在继续执行任何工作之前首先获取对象上的监视器。它与同步块使用的互斥模型不同。
等待通知或相互协作模型通常用于生产者 - 消费者场景,其中一个线程产生由另一个线程消耗的事件。精心编写的实现将努力避免消费者缺乏或生产者用太多事件超出消费者的情况。为避免这种情况,您可以使用wait-notify协议
wait
。notifies
消费者,然后通常进入睡眠状态,直到消费者notified
为止。notifies
处理完事件。在这种情况下,您可以拥有许多生产者和消费者。在wait
,notify
或notifyAll
上通过互斥模型获取监视器必然会破坏此模型,因为生产者和消费者不会明确地执行等待。底层线程将出现在监视器的等待集(由wait-notify模型使用)或条目集(由互斥模型使用)中。调用notify
或notifyAll
信号线程将从等待集移动到监视器的入口集(在几个线程中可能存在争用监视器的情况,而不仅仅是最近的通知一个)。
现在,当您想使用互斥模式在wait
,notify
和notifyAll
上自动获取监视器时,通常表明您不需要使用wait-notify模型。这是通过推理 - 你通常只有在一个线程中做一些工作之后,即在状态改变时才发出信号通知其他线程。如果您自动获取监视器并调用notify
或notifyAll
,则只是将线程从等待集移动到条目集,而程序中没有任何中间状态,这意味着不需要转换。很明显,JVM的作者已经意识到这一点,并且没有将这些方法声明为同步。
您可以在Bill Venner的书中了解有关监视器的等待集和入口集的更多信息 - Inside the Java Virtual Machine。
答案 6 :(得分:0)
我认为wait
没有synchronized
可以在某些情况下运作良好。但如果没有竞争条件,它就无法用于复杂的场景,可能会出现“虚假唤醒”。
代码适用于队列。
// 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
}
此代码没有解释共享数据的状态,它将忽略最后一个元素,并且可能无法在多线程条件下工作。如果没有竞争条件,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
}