为什么wait / notify / notifyAll方法在java中不同步?
在Java中,每当我们需要调用wait / notify / notifyAll时,我们需要访问对象监视器(通过synchronized方法或通过synchronized块)。 所以我的问题是为什么java没有去同步等待/通知方法去除从同步块或方法调用这些方法的限制。
如果它们被声明为synchronized,它将自动获取监视器访问权限。
对于notify和notifyAll,您的想法的问题是,当您通知您时,您通常也会在同一个同步块中执行其他操作。 因此,使notify方法同步不会给你任何东西,你仍然需要块。 同样,等待必须在同步块或方法中才能有用,例如在自旋锁内,无论如何必须同步测试。 所以锁定的粒度对你的建议都是错误的。
这是一个例子,这是关于Java中最简单的队列实现:
public class MyQueue { private List list = new ArrayList (); 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块中完成。
在这两种情况下,需要在保持锁定的情况下进行围绕等待的检查,以便当代码根据这些检查采取行动时,它知道这些结果当前是有效的。
好问题。 JDK7对象实现中的注释对此有所启发 ,我认为(强调我的):
此方法使当前线程(称为
T
)将自身置于此对象的等待集中,然后放弃此对象上的任何和所有同步声明 。…
然后以这种通常的方式从等待集中删除线程
T
,其他线程使权利在对象上同步; 一旦它获得了对象的控制权, 它对对象的所有同步声明都将恢复到原状 – 即,调用wait
方法时的情况 。 线程T
然后从wait
方法的调用返回 。 因此,从wait
方法返回时,对象和线程T
的同步状态与调用wait
方法时的状态完全相同。
所以我认为要注意的第一点是wait()
在调用者完成等待之前不会返回(显然)。 这意味着如果wait()
本身被同步,那么调用者将继续保持对象的锁定,并且没有其他人能够wait()
或notify()
。
现在很明显, wait()
在幕后做了一些棘手的事情来迫使调用者无论如何都失去了对对象锁的所有权,但是如果wait()
也许这个技巧不会起作用(或者工作会更加困难wait()
本身是同步的。
第二点是,如果多个线程在一个对象上等待,当使用notify()
来唤醒其中一个时,标准争用方法用于只允许一个线程在对象上同步,并且wait()
被认为是将调用者的同步声明恢复到调用wait()
之前的确切状态。 在我调用wait()
之前要求调用者保持锁定似乎可以简化这一点,因为它不需要在wait()
返回后检查调用者是否应该继续持有锁。 合同规定调用者必须继续持有锁,因此简化了一些实现。
或者可能只是为了避免出现逻辑悖论“如果wait()
和notify()
都是同步的,并且wait()
在调用notify()
之前不会返回,如何才能成功使用?”。
无论如何,这些都是我的想法。
我的猜测是需要synchronized
块的原因是使用wait()
或notify()
作为synchronized
块中的唯一动作几乎总是一个bug。
Findbugs甚至对此有一个警告,它称之为“ 裸体通知 ”。
在我读过和写入的所有非错误代码中,所有这些代码都在更大的同步块中使用wait/notify
,包括读/写其他条件
synchronized(lock) update condition lock.notify() synchronized(lock) while( condition not met) lock.wait()
如果wait/notify
本身已synchronized
,则不会对所有正确的代码造成任何损害(可能会造成很小的性能损失); 它对所有正确的代码都没有任何好处。
但是,它会允许并鼓励更多错误的代码。
对multithreading更有经验的人应该随意介入,但我相信这会消除同步块的多function性。 使用它们的目的是在作为受监视资源/信号量的特定对象上进行同步。 然后使用wait / notify方法来控制synchronized块中的执行流。
请注意,同步方法是在方法(或静态方法的类)的持续时间内this
进行同步的简写。 同步等待/通知方法本身将删除它们在线程之间用作停止/运行信号的点。
等待通知同步模型要求您在继续执行任何工作之前首先获取对象上的监视器。 它与同步块使用的互斥模型不同。
等待通知或相互协作模型通常用于生产者 – 消费者场景中,其中一个线程产生由另一个线程消耗的事件。 精心编写的实现将努力避免消费者缺乏或生产者用太多事件超出消费者的情况。 为避免这种情况,您可以在其中使用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中阅读有关监视器的等待集和入口集的更多信息。
我认为没有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 }
此代码没有解释共享数据的状态,它将忽略最后一个元素,并且可能无法在multithreading条件下工作。 没有竞争条件, 1
这个列表可能具有完全不同的状态。
// 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 }
参考Chris Smith 参考insidevm