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

Ash*_*ish 23 java multithreading synchronization synchronized

在Java中,每当我们需要调用wait/notify/notifyAll时,我们需要访问对象监视器(通过synchronized方法或通过synchronized块).所以我的问题是为什么java没有去同步等待/通知方法去除从同步块或方法调用这些方法的限制.

如果将它们声明为synchronized,它将自动获取监视器访问权限.

Nat*_*hes 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @aroth由于虚假醒来,你必须在`wait()`之后重新检查条件.因为同时触及条件,这意味着`wait()`必须在更大的`synchronized`块内. (6认同)
  • @Nathan是的,大部分时间你的`wait`都在某个条件检查循环中:`while(conditionVar){... o.wait()...}`其中`conditionVar`由其他一些线程设置,这也会触发`notify`.如果第二个线程在原始线程检查了conditionVar但尚未开始等待时执行其业务,则会遇到严重问题.如果整个`while`循环在synchronized块中(没有竞争条件),则不会发生这种情况. (3认同)
  • @aroth更严重的问题是在进入等待之前检查条件.它必须完成,并且必须以原子方式完成(检查并在同一锁下等待).否则你可能会错过notify(),服务员永远不会醒来.我不相信有一个用例可以正确使用"裸等待". (2认同)

aro*_*oth 7

好问题.JDK7对象实现中的注释对此有所启发,我认为(强调我的):

此方法使当前线程(调用它T)将自身置于此对象的等待集中,然后放弃此对象上的所有同步声明.

...

该线程T然后,从用于与其他线程用于向对象上同步右侧此通常的方式等待集合中移除; 一旦它获得对象的控制权,对象的所有同步声明都将恢复到原状 - 即,wait 调用方法时的情况.线程T然后从wait方法的调用返回.因此,从wait方法返回 时,对象和线程的同步状态与调用方法T时的状态 完全相同wait.

所以我认为要注意的第一点是,wait()在调用者完成等待之前不会返回(显然).这意味着如果wait()它本身是同步的,那么调用者将继续保持对象的锁定,并且没有其他人能够wait()或notify().

现在显然wait()是在幕后做一些棘手的事情来迫使调用者失去对对象锁的所有权,但是如果wait()它本身是同步的话,也许这个技巧不会起作用(或者工作起来要困难得多).

第二点是,如果多个线程正在等待一个对象,当notify()用于准确唤醒其中一个时,标准争用方法用于仅允许一个线程在对象上同步,并且wait()应该恢复调用者的同步声明到达之前他们所处的确切状态wait().在我看来,在调用之前要求调用者持有锁可以wait()简化这一点,因为它不需要检查调用者是否应该或者不应该在wait()返回后继续持有锁.合同规定调用者必须继续持有锁,因此简化了一些实现.

或者也许只是为了避免出现逻辑悖论"如果wait()和notify()都是同步的,并且wait()直到notify()被调用才返回,如何才能成功使用?".

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


Pau*_*ora 1

对多线程更有经验的人应该可以随意介入,但我相信这会消除同步块的多功能性。使用它们的目的是在充当受监视资源/信号量的特定对象上进行同步。然后使用等待/通知方法来控制同步块内的执行流程。

this请注意,同步方法是在方法(或静态方法的类)持续时间内同步的简写。同步等待/通知方法本身将消除它们用作线程之间的停止/执行信号的意义。