为什么java中wait/notify/notifyAll方法不同步?

发布于 2024-11-29 03:22:40 字数 172 浏览 2 评论 0原文

在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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

绅刃 2024-12-06 03:22:40

对于notify和notifyAll,您的想法的问题在于,当您通知时,您通常还会在同一个同步块中执行其他操作。因此,使通知方法同步不会给你带来任何好处,你仍然需要该块。同样,wait 必须位于同步块或方法中才能有用,例如位于自旋锁内,无论如何测试都必须同步。因此,锁定的粒度对于您的建议来说是完全错误的。

下面是一个例子,这是 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();
        }
    }
}

因此,您可以使用生产者线程将内容添加到队列中,使用消费者线程将内容取出。当线程要从队列中获取某些内容时,它需要在同步块内检查列表中是否有某些内容,一旦收到通知,它需要重新获取锁并确保列表中仍然有某些内容(因为某些其他消费者线程可能会介入并抓住它)。还有“虚假唤醒”现象:你不能依赖被唤醒作为发生了某些事情的充分证据,你需要检查你正在等待的任何条件因为这实际上是真的,这需要在同步块内完成。

在这两种情况下,都需要在持有锁的情况下进行围绕等待的检查,以便当代码根据这些检查采取操作时,它知道这些结果当前是有效的。

(如果您的用例没有如上所述需要更改的状态,则同步可能是不适合该工作的工具。使用其他一些方法,例如 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:

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();
        }
    }
}

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.)

夜还是长夜 2024-12-06 03:22:40

好问题。 JDK7 对象实现 中的注释阐明了一些情况我认为(强调我的):

此方法导致当前线程(称之为T)放置
本身处于该对象的等待集中,然后放弃任何
以及此对象上的所有同步声明

...

然后线程T将从等待集中删除
通常的方式与其他线程同步的权利
目的;一旦它获得了对物体的控制,它的所有
对象上的同步声明恢复到原状
ante - 即 wait 时的情况
方法被调用
线程 T 然后从
调用wait方法
。因此,从
wait方法,对象与线程的同步状态
T 与调用 wait 方法时完全相同
调用。

所以我认为首先要注意的一点是 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):

This method causes the current thread (call it T) to place
itself in the wait set for this object and then to relinquish any
and all synchronization claims on this object
.

...

The thread T is then removed from the wait set for this
usual manner with other threads for the right to synchronize on the
object; once it has gained control of the object, all its
synchronization claims on the object are restored to the status quo
ante - that is, to the situation as of the time that the wait
method was invoked
. Thread T then returns from the
invocation of the wait method
. Thus, on return from the
wait method, the synchronization state of the object and of thread
T is exactly as it was when the wait method was
invoked.

So I think the first point to note is that wait() doesn't return until the caller is done waiting (obviously). Meaning that if wait() itself were synchronized then the caller would continue to hold the lock on the object, and nobody else would be able to wait() or notify().

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) if wait() 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 that wait() is supposed to restore the caller's synchronization claims to the exact state they were in prior to the call to wait(). It seems possible to me that requiring the caller to hold the lock prior to calling wait() simplifies this, as it removes the need to check to see if the caller should or should not continue holding the lock after wait() 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() and notify() are both synchronized, and wait() doesn't return until notify() is called, how can either ever be used successfully?".

Those are my thoughts, anyways.

梦与时光遇 2024-12-06 03:22:40

我的猜测是,需要 synchronized 块的原因是使用 wait()notify() 作为无论如何,同步块几乎总是一个错误。

Findbugs 甚至对此有一个警告,它称之为“裸通知"。

My guess would be that the reason the synchronized block is required is that the use of wait() or notify() as the only action in a synchronized block is almost always a bug anyway.

Findbugs even has a warning for this, which it calls a "naked notify".

谈情不如逗狗 2024-12-06 03:22:40

在我读过和写过的所有无错误代码中,它们都在更大的同步块中使用了 wait/notify ,涉及其他条件的读/写

synchronized(lock)
    update condition
    lock.notify()

synchronized(lock)
    while( condition not met)
        lock.wait()

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 conditions

synchronized(lock)
    update condition
    lock.notify()

synchronized(lock)
    while( condition not met)
        lock.wait()

If wait/notify are themselves synchronized, 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.

尛丟丟 2024-12-06 03:22:40

对多线程更有经验的人应该可以随意介入,但我相信这会消除同步块的多功能性。使用它们的目的是在充当受监视资源/信号量的特定对象上进行同步。然后使用 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.

纸伞微斜 2024-12-06 03:22:40

同步的等待通知模型要求您在继续执行任何工作之前首先获取对象上的监视器。它与同步块使用的互斥模型不同。

等待通知或相互合作模型通常用于生产者-消费者场景,其中一个线程生成由另一个线程消费的事件。编写良好的实现将努力避免消费者饥饿或生产者因太多事件而超出消费者的情况。为了避免这种情况,您可以使用等待通知协议,其中

  • 消费者等待生产者生成事件。
  • 生产者生成事件并通知消费者,然后通常会进入休眠状态,直到消费者通知
  • 当消费者收到事件通知时,它会醒来,处理该事件,并通知生产者它已完成对事件的处理。

在这种情况下,您可能有许多生产者和消费者。通过互斥模型获取监视器,在 waitnotifynotifyAll 上必然会破坏此模型,因为生产者和消费者都这样做不显式执行等待。底层线程将出现在监视器的等待集(由等待通知模型使用)或条目集(由互斥模型使用)中。调用 notifynotifyAll 向线程发出信号,将其从等待集移动到监视器的条目集(其中多个线程之间可能存在监视器争用) ,而不仅仅是最近通知的)。

现在,当您想要使用互斥模型自动获取 waitnotifynotifyAll 上的监视器时,通常表明您不需要使用等待通知模型。这是推断 - 通常,只有在一个线程中完成一些工作(即状态发生变化)后,您才会向其他线程发出信号。如果您自动获取监视器并调用 notifynotifyAll,您只是将线程从等待集移动到入口集,程序中没有任何中间状态,这意味着认为过渡是不必要的。很明显,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

  • the consumer waits for the producer to produce an event.
  • the producer produces the event and notifies the consumer, and then usually goes to sleep, until it is notified by the consumer.
  • when the consumer is notified of an event, it wakes up, processes the event and the 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 or notifyAll 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. Invoking notify or notifyAll 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 and notifyAll 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 invoking notify or notifyAll, 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.

血之狂魔 2024-12-06 03:22:40

我认为没有 synchronizedwait 在某些情况下可以很好地工作。但它不能用于没有竞争条件的复杂场景,可能会发生“虚假唤醒”。

代码适用于队列。

// 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
}

此代码没有解释共享数据的状态,它将忽略最后一个元素,并且可能无法在多线程条件下工作。如果没有竞争条件,12 中的列表可能具有完全不同的状态。

// consumer
take(){
  while(list.isEmpty()) // 1
    lock.wait()
  return list.remove(0) // 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)

“虚假唤醒”发生,也会使代码不可预测。

// 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 参考

I think wait without synchronized 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.

// 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
}

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.

// consumer
take(){
  while(list.isEmpty()) // 1
    lock.wait()
  return list.remove(0) // 2
}

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.

// 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
}

Reference of Chris Smith
Reference of insidevm

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文