Запутанная причина пробуждения потока, когда условие не выполняется

#java #multithreading #concurrency

#java #многопоточность #параллелизм

Вопрос:

В настоящее время я читаю эффективную Java, и я нахожусь в главе о параллелизме. При объяснении причин, по которым поток может проснуться, когда условие не выполняется (условие цикла while, в котором выполняется вызов wait()), есть одна причина, которая меня довольно смущает, и я, похоже, не могу ее понять.

Другой поток мог получить блокировку и изменить защищенное состояние между моментом, когда поток вызвал уведомление, и ожидающий поток проснулся.

Может кто-нибудь попытаться объяснить это предложение?

Ответ №1:

Это проще всего объяснить на примере:

 public class ConditionTest implements Runnable {
    
    private boolean flag;
    
    public void run() {
        try {
            synchronized (this) {
                while (true) {
                    this.wait();
                    if (flag) {
                        System.out.printf("%s: my condition is truen",
                                          Thread.currentThread().getName());
                        flag = false;
                    } else {
                        System.out.printf("%s: my condition is false!!n",
                                          Thread.currentThread().getName());
                    }
                }
            }
        } catch (InterruptedException ex) {
             // bail out
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        ConditionTest ct = new ConditionTest();
        new Thread(ct).start();
        new Thread(ct).start();
        new Thread(ct).start();
    
        Thread.sleep(1000);
    
        while (true) {
            synchronized(ct) {
                ct.flag = true;
                ct.notifyAll();
            }
            Thread.sleep(1000);
        }
    }
}
  

Условием в этом примере является то, что runnable flag должно быть true . Рабочие потоки сообщают о состоянии условия, а затем сбрасывают флаг.

Как вы можете видеть, метод main создал и запустил три потока, совместно использующих runnable. Затем он повторно устанавливает флаг в true и уведомляет рабочих.

Когда рабочий разбужен, он может обнаружить, что флаг является false ; т. Е. Его условие не выполняется. На самом деле, если вы запустите приведенный выше код, вы увидите, что это происходит два раза из трех.

Почему?

Потому что, если рабочий видит это flag == true , он снимает флаг! Поэтому, когда другие рабочие просыпаются, они видят очищенный флаг.

Это то, о чем говорится в тексте в кавычках.

По общему признанию, в данном случае это вызвано нашим сомнительным notifyAll вызовом, но применяется общий принцип. Вы должны проверить условие.


На некоторых платформах также возможно (или было) возникновение ложных уведомлений; т. Е. Уведомлений, Которые не являются результатом какого-либо notify или notifyAll вызова из кода приложения.