同步,wait/notifyAll 必须在同一个对象上,但为什么呢?

synchronized, wait/notifyAll has to be on the same object, but why?

当我尝试使用 wait() with synchronized 进行简单演示时,我突然想到了一件有趣的事情,下面的演示给了我 unexpected 输出。

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static synchronized void send() {
        try {
            while (!isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        num.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " number updated: " + num);
        setConsumed(false);
        num.notifyAll();
    }

    private static synchronized void receive() {
        try {
            while (isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " number received: " + num);
        setConsumed(true);
        num.notifyAll(); // ToDo: when to use notify?
        // ToDo: what is monitor?
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}

输出不稳定,但有一个典型的可以

shared-pool-0 number received: 0
shared-pool-1 number updated: 1
shared-pool-0 number received: 1
shared-pool-1 number updated: 2
shared-pool-1 number received: 2
shared-pool-2 number updated: 3

虽然我期待的是

shared-pool-1 number received: 0
shared-pool-0 number updated: 1
shared-pool-3 number received: 1
shared-pool-2 number updated: 2
shared-pool-1 number received: 2
shared-pool-0 number updated: 3
shared-pool-2 number received: 3
shared-pool-3 number updated: 4
shared-pool-5 number received: 4
shared-pool-4 number updated: 5

当我在 wait()/notifyAll() 上使用 WaitZero.class 而不是 num 时,检索到了正确的结果。

我看了一圈,似乎总是必须在 同一个对象 上使用其中的三个以确保正确性。

我的猜测:如果不是所有的都在同一个对象上,notifyAll()和同步锁之间有一个特殊情况。但它是什么?

任何帮助将不胜感激;)

在@JB Nizet、@Amardeep Bhowmick 和所有人的大量天真问题和大力帮助之后,我从 How to work with wait(), notify() and notifyAll() in Java? 中找到了一个精辟的句子,准确地解释了原因。

wait()方法是designed/used放弃锁(因为某些条件不满足)让其他线程work/cooperate;一个典型的用例是 sender/receiver 或 producer/consumer.

wait()

It tells the calling thread to give up the lock and go to sleep until some other thread enters the same monitor and calls notify()...The wait() method is actually tightly integrated with the synchronization lock, using a feature not available directly from the synchronization mechanism.

synchronized(lockObject) {
    while( ! condition ) {
        lockObject.wait();
    }
    //take the action here;
}

在这种情况下,问题可以简单地按以下方式解决,或者只使用 WaitZero.class 代替 wait/notifyAll

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static void send() {
        synchronized (num) {
            try {
                while (!isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            num.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + " number updated: " + num);
            setConsumed(false);
            num.notifyAll();
        }
    }

    private static void receive() {
        synchronized (num) {
            try {
                while (isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " number received: " + num);
            setConsumed(true);
            num.notifyAll(); // ToDo: when to use notify?
            // ToDo: what is monitor?
        }
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}