синхронизировано, wait / notifyAll должно быть на одном объекте, но почему? - PullRequest
0 голосов
/ 30 марта 2019

Одна интересная вещь произошла со мной, когда я пытался сделать простую демонстрацию, используя wait() с synchronized, следующая демонстрация дает мне неожиданное выходы.

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

Получен правильный результаткогда я использую WaitZero.class вместо num на wait()/notifyAll().

Я прочитал вокруг, и кажется, что всегда нужно использовать три из них на одном и том же объекте , чтобы гарантировать правильность.

Угадай: Если не все из них на одном и том же объекте, между notifyAll() и синхронизированной блокировкой существует особый случай.Но что это?

Любая помощь будет оценена;)

1 Ответ

0 голосов
/ 30 марта 2019

После множества наивных вопросов и огромной помощи от @JB Nizet, @Amardeep Bhowmick и всего прочего я нашел содержательное предложение из Как работать с wait (), notify () и notifyAll () в Java? объясните причину точно.

wait() метод предназначен / используется для снятия блокировки (поскольку некоторые условия не выполнены), чтобы позволить другим потокам работать / взаимодействовать;Типичный вариант использования - отправитель / получатель или производитель / потребитель.

wait()

Он сообщает вызывающему потоку отказаться от блокировки и перейти в спящий режим, пока какой-либо другой поток не войдет в того же монитора и вызовы notify() ... Метод wait() на самом деле тесно интегрирован с блокировкой синхронизации, используя функцию, недоступную непосредственно из механизма синхронизации.

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;
    }
}
...