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

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

如果这些被声明为已同步,它将自动获取监视器访问权限。


答案 1

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

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

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


答案 2

问得好。我认为JDK7对象实现中的评论对此有所了解(强调我的):

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

...

然后,该线程将从等待集中删除,以这种通常的方式与其他线程一起同步对象的权限;一旦它获得了对对象的控制权,它对该对象的所有同步声明都将恢复到原状 - 即,恢复到调用等待方法时的情况然后,线程 T等待方法的调用中返回。因此,从方法返回时,对象和线程的同步状态与调用方法时完全相同。TwaitTwait

所以我认为要注意的第一点是,在调用方完成等待之前不会返回(显然)。这意味着,如果自身已同步,则调用方将继续保持对对象的锁定,而其他人将无法或 。wait()wait()wait()notify()

现在显然在幕后做了一些棘手的事情,迫使调用方无论如何都失去了对对象锁定的所有权,但是如果本身是同步的,也许这个技巧将不起作用(或者制作起来会更加困难)。wait()wait()

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

或者,这样做只是为了避免出现“如果 和 都同步,并且在调用之前不返回,那么这两者怎么可能被成功使用?”的逻辑悖论的出现。wait()notify()wait()notify()

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