繁体   English   中英

为什么等待/通知/通知所有方法在 java 中不同步?

[英]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()被调用时才返回,那么如何成功使用两者”的逻辑悖论的出现?”。

无论如何,这些是我的想法。

我的猜测是,需要synchronized块的原因是使用wait()notify()作为synchronized块中的唯一操作几乎总是一个错误。

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

在我读过和写过的所有非错误代码中,它们都在更大的同步块中使用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 获取监视器,在waitnotifynotifyAll时必然会销毁此 model,因为生产者和消费者没有显式执行等待。 底层线程将出现在监视器的等待集(由等待通知模型使用)或条目集(由互斥模型使用)中。 调用notifynotifyAll信号线程从等待集移动到监视器的条目集(其中可能存在多个线程之间的监视器争用,而不仅仅是最近通知的线程)。

现在,当您想使用互斥 model 在waitnotifynotifyAll上自动获取监视器时,通常表明您不需要使用 wait-notify model。 这是通过推理得出的——您通常只会在一个线程中完成一些工作之后,即在 state 发生更改后,才向其他线程发出信号。 如果您自动获取监视器并调用notifynotifyAll ,您只是将线程从等待集移动到条目集,程序中没有任何中间 state,这意味着过渡是不必要的。 很明显,JVM 的作者意识到了这一点,并没有将这些方法声明为同步。

您可以在 Bill Venner 的书 - Inside the Java Virtual Machine中阅读有关监视器的等待集和入口集的更多信息。

我认为没有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
}

这段代码没有解释共享数据的 state,它会忽略最后一个元素,在多线程条件下可能不起作用。 如果没有竞争条件,则12中的列表可能具有完全不同的状态。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM