为什么java中wait/notify/notifyAll方法不同步?
在Java中,每当我们需要调用wait/notify/notifyAll时,我们都需要访问对象监视器(通过synchronized方法或通过synchronized块)。所以我的问题是为什么java不采用同步等待/通知方法来消除从同步块或方法调用这些方法的限制。
如果这些被声明为同步,它将自动获取监视器访问权限。
in Java whenever we need to call wait/notify/notifyAll, we need to have access to object monitor (either through synchronized method or through synchronized block). So my question is why java didn't go for synchronized wait/notify methods removing the restriction of calling these methods from synchronized block or methods.
In case these are declared as synchronized, it would have automatically taken the monitor access.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
对于notify和notifyAll,您的想法的问题在于,当您通知时,您通常还会在同一个同步块中执行其他操作。因此,使通知方法同步不会给你带来任何好处,你仍然需要该块。同样,wait 必须位于同步块或方法中才能有用,例如位于自旋锁内,无论如何测试都必须同步。因此,锁定的粒度对于您的建议来说是完全错误的。
下面是一个例子,这是 Java 中最简单的队列实现:
因此,您可以使用生产者线程将内容添加到队列中,使用消费者线程将内容取出。当线程要从队列中获取某些内容时,它需要在同步块内检查列表中是否有某些内容,一旦收到通知,它需要重新获取锁并确保列表中仍然有某些内容(因为某些其他消费者线程可能会介入并抓住它)。还有“虚假唤醒”现象:你不能依赖被唤醒作为发生了某些事情的充分证据,你需要检查你正在等待的任何条件因为这实际上是真的,这需要在同步块内完成。
在这两种情况下,都需要在持有锁的情况下进行围绕等待的检查,以便当代码根据这些检查采取操作时,它知道这些结果当前是有效的。
(如果您的用例没有如上所述需要更改的状态,则同步可能是不适合该工作的工具。使用其他一些方法,例如 CountdownLatch,可能会为您提供更简单的解决方案。)
For notify and notifyAll, the problem with your idea is that when you notify you also have other stuff you're typically doing in the same synchronized block. So making the notify method synchronized wouldn't buy you anything, you'd still need the block. Likewise wait has to be in a synchronized block or method in order to be useful, such as being inside a spinlock where the test has to be synchronized anyway. So the granularity of locking is all wrong for what you suggest.
Here's an example, this is about the simplest queue implementation you can have in Java:
So you can have producer threads that add things to the queue and consumer threads that take things out. When a thread goes to get something out of the queue it needs to check within the synchronized block that there's something in the list, and once it's been notified it needs to reacquire the lock and make sure there is still something in the list (because some other consumer thread could have stepped in and grabbed it).There's also the "spurious wake-up" phenomenon: you can't rely on getting woken up as sufficient evidence that something happened, you need to check that whatever condition you're waiting for is actually true, and that needs to be done within the synchronized block.
In both of these cases, checks surrounding the wait need to be made with the lock held so that when the code takes action based on those checks it knows that those results are currently valid.
(If your use case does not have state that needs changing as described above, synchronized is probably the wrong tool for the job. Using some other means, such as CountdownLatch, may give you a simpler solution.)
好问题。 JDK7 对象实现 中的注释阐明了一些情况我认为(强调我的):
所以我认为首先要注意的一点是
wait()
在调用者完成等待之前不会返回(显然)。这意味着如果wait()
本身是同步的,那么调用者将继续持有该对象的锁,并且其他人都无法wait()
或通知()。
现在显然
wait()
正在幕后做一些棘手的事情,以强制调用者无论如何都失去对象锁的所有权,但也许这个技巧不起作用(或者会更难实现) make work) 如果wait()
本身已同步。第二点是,如果多个线程正在等待一个对象,当使用
notify()
来唤醒其中一个线程时,将使用标准争用方法来仅允许一个线程在该对象上同步,并且wait()
应该将调用者的同步声明恢复到调用wait()
之前的确切状态。在我看来,要求调用者在调用 wait() 之前持有锁可能会简化这一点,因为它不需要检查调用者是否应该在调用之后继续持有锁。wait()
返回。合约规定调用者必须继续持有锁,因此简化了一些实现。或者可能只是为了避免出现“如果
wait()
和notify()
都是同步的,并且wait()<”的逻辑悖论。 /code> 在调用
notify()
之前不会返回,如何才能成功使用?”。无论如何,这些是我的想法。
Good question. The comments in the JDK7 Object implementation shed some light on this, I think (emphasis mine):
So I think the first point to note is that
wait()
doesn't return until the caller is done waiting (obviously). Meaning that ifwait()
itself were synchronized then the caller would continue to hold the lock on the object, and nobody else would be able towait()
ornotify()
.Now obviously
wait()
is doing something tricky behind the scenes to force the caller to lose its ownership of the lock on the object anyways, but perhaps this trick would not work (or would be significantly more difficult to make work) ifwait()
itself were synchronized.The second point is that if multiple threads are waiting on an object, when
notify()
is used to wake exactly one of them the standard contention method is used to allow only one thread to synchronize on the object, and thatwait()
is supposed to restore the caller's synchronization claims to the exact state they were in prior to the call towait()
. It seems possible to me that requiring the caller to hold the lock prior to callingwait()
simplifies this, as it removes the need to check to see if the caller should or should not continue holding the lock afterwait()
returns. The contract stipulates that the caller must continue holding the lock, and thus some of the implementation is simplified.Or perhaps it was simply done to avoid the appearance of the logical paradox of "if
wait()
andnotify()
are both synchronized, andwait()
doesn't return untilnotify()
is called, how can either ever be used successfully?".Those are my thoughts, anyways.
我的猜测是,需要
synchronized
块的原因是使用wait()
或notify()
作为无论如何,同步块几乎总是一个错误。Findbugs 甚至对此有一个警告,它称之为“裸通知"。
My guess would be that the reason the
synchronized
block is required is that the use ofwait()
ornotify()
as the only action in asynchronized
block is almost always a bug anyway.Findbugs even has a warning for this, which it calls a "naked notify".
在我读过和写过的所有无错误代码中,它们都在更大的同步块中使用了
wait/notify
,涉及其他条件的读/写If
wait/notify
> 本身同步
,不会对所有正确的代码造成任何损害(可能会造成很小的性能损失);它对所有正确的代码也没有任何好处。然而,它会允许并鼓励更多的错误代码。
Among all the non-buggy codes I've read and written, all the them use
wait/notify
in a bigger synchronization block involving read/write of other conditionsIf
wait/notify
are themselvessynchronized
, no harm is done to all the correct codes (may a small performance penalty); it won't do any good either to all the correct codes.However, it would have permitted and encouraged a lot more incorrect codes.
对多线程更有经验的人应该可以随意介入,但我相信这会消除同步块的多功能性。使用它们的目的是在充当受监视资源/信号量的特定对象上进行同步。然后使用 wait/notify 方法来控制同步块内的执行流程。
请注意,同步方法是在方法(或静态方法的类)持续时间内同步
this
的简写。同步等待/通知方法本身将消除它们用作线程之间的停止/执行信号的意义。Someone more experienced with multithreading should feel free to step in, but I believe this would remove the versatility of synchonized blocks. The point of using them is to synchronize on a particular object which functions as the monitored resource/semaphore. wait/notify methods are then used to control execution flow within the synchronized block.
Note that synchronized methods are shorthand for synchronizing on
this
for the duration of the method (or the class for static methods). Synchronizing wait/notify methods themselves would remove the point of their use as stop/go signals between threads.同步的等待通知模型要求您在继续执行任何工作之前首先获取对象上的监视器。它与同步块使用的互斥模型不同。
等待通知或相互合作模型通常用于生产者-消费者场景,其中一个线程生成由另一个线程消费的事件。编写良好的实现将努力避免消费者饥饿或生产者因太多事件而超出消费者的情况。为了避免这种情况,您可以使用等待通知协议,其中
等待
生产者生成事件。通知
消费者,然后通常会进入休眠状态,直到消费者通知
。通知
生产者它已完成对事件的处理。在这种情况下,您可能有许多生产者和消费者。通过互斥模型获取监视器,在
wait
、notify
或notifyAll
上必然会破坏此模型,因为生产者和消费者都这样做不显式执行等待。底层线程将出现在监视器的等待集(由等待通知模型使用)或条目集(由互斥模型使用)中。调用notify
或notifyAll
向线程发出信号,将其从等待集移动到监视器的条目集(其中多个线程之间可能存在监视器争用) ,而不仅仅是最近通知的)。现在,当您想要使用互斥模型自动获取
wait
、notify
和notifyAll
上的监视器时,通常表明您不需要使用等待通知模型。这是推断 - 通常,只有在一个线程中完成一些工作(即状态发生变化)后,您才会向其他线程发出信号。如果您自动获取监视器并调用notify
或notifyAll
,您只是将线程从等待集移动到入口集,程序中没有任何中间状态,这意味着认为过渡是不必要的。很明显,JVM 的作者意识到了这一点,并且没有将这些方法声明为同步的。您可以在 Bill Venner 的书中阅读有关监视器的等待集和入口集的更多信息 - Inside the Java虚拟机。
The wait-notify model of synchronization requires that you acquire the monitors on an object first, before proceeding to perform any work. It is different from the mutual exclusion model that is used by a synchronized block.
The wait-notify or mutual cooperation model is typically used in a producer-consumer scenario where one thread produces events that are consumed by another thread. Well-written implementations will strive to avoid the scenarios where a consumer is starved or the producer overruns the consumer with too many events. To avoid this, you would use the wait-notify protocol where
wait
s for the producer to produce an event.notifies
the consumer, and then usually goes to sleep, until it isnotified
by the consumer.notifies
the producer that it has finished processing the event.You could have many producers and consumers in this scenario. Acquiring the monitor through the mutual exclusion model, on
wait
,notify
ornotifyAll
is bound to destroy this model, since the producer and the consumer do not perform the waiting explicitly. The underlying threads will be present in either the wait set (used by the wait-notify model) or the entry set (used by the mutual exclusion model) of a monitor. Invokingnotify
ornotifyAll
signals thread(s) to be moved from the wait set to the entry set of the monitor (where there may be contention for the monitor, among several threads, and not just the recently notified one).Now, when you want to automatically acquire the monitor on
wait
,notify
andnotifyAll
using the mutual exclusion model, it is usually an indication that you do not need to be using the wait-notify model. This is by inference - you would typically signal other threads, only after doing some work in one thread, i.e. on a change in state. If you are automatically acquiring the monitor and invokingnotify
ornotifyAll
, you are merely moving threads from the wait set to the entry set, without any intermediate state in your program, implying that the transition is unnecessary. Quite obviously, the authors of the JVM were aware of this, and have not declared the methods as synchronized.You can read more about the wait sets and entry sets of monitors in Bill Venner's book - Inside the Java Virtual Machine.
我认为没有
synchronized
的wait
在某些情况下可以很好地工作。但它不能用于没有竞争条件的复杂场景,可能会发生“虚假唤醒”。代码适用于队列。
此代码没有解释共享数据的状态,它将忽略最后一个元素,并且可能无法在多线程条件下工作。如果没有竞争条件,
1
、2
中的列表可能具有完全不同的状态。现在,让它变得更加复杂和明显。
执行指令
give(element) lock.notify()->take() lock.wait()复活->take() list.remove(0)->rollback(element)
give(element) lock.notify()->take() lock.wait()复活->rollback(element)->take() list.remove(0)
“虚假唤醒”发生,也会使代码不可预测。
Chris Smith 的参考
insidevm 参考
I think
wait
withoutsynchronized
can work good in some scenario. But it can't be used for complicated scenario without race condition, "spurious wakeups" might occur.Code works for the queue.
This code haven't explain the state of shared data, it will ignore the last element and may not work in mutil-thread condition. Without race condition, this list in
1
,2
may have totally different states.And now, makes it a little more complicated and obvious.
execution of instruction
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)
"spurious wakeups" occur, also makes code unpredicatable.
Reference of Chris Smith
Reference of insidevm