Java:уведомлять() противnotifyAll() все сначала

StackOverflow https://stackoverflow.com/questions/37026

  •  09-06-2019
  •  | 
  •  

Вопрос

Если кто-то загуглит в поисках "разницы между notify() и notifyAll()" затем появится множество объяснений (оставляя в стороне абзацы javadoc).Все это сводится к количеству ожидающих пробуждения потоков:один в notify() и все это в notifyAll().

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

Что это за полезный разница между уведомить() и Уведомлять всех () тогда?Я что-то упускаю?

Это было полезно?

Решение

  

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

Это не правильно. o.notifyAll () пробуждает все потоков, заблокированных в вызовах o.wait () . Потокам разрешено возвращаться из o.wait () только один за другим, но каждый из них будет получать свою очередь.

<Ч>

Проще говоря, это зависит от того, почему ваши темы ожидают уведомления. Вы хотите сообщить одному из ожидающих потоков о том, что что-то произошло, или вы хотите сообщить обо всех этих событиях одновременно?

В некоторых случаях все ожидающие потоки могут предпринять полезные действия после завершения ожидания. Примером может служить набор потоков, ожидающих завершения определенной задачи; По завершении задачи все ожидающие потоки могут продолжить свою работу. В таком случае вы будете использовать notifyAll () для пробуждения всех ожидающих потоков одновременно.

В другом случае, например, взаимоисключающей блокировке, только один из ожидающих потоков может сделать что-то полезное после получения уведомления (в этом случае получить блокировку). В таком случае вы бы предпочли использовать notify () . При правильной реализации вы могли бы использовать notifyAll () и в этой ситуации, но вы бы без необходимости разбудили потоки, которые ничего не могут сделать.

<Ч>

Во многих случаях код для ожидания условия будет записан в виде цикла:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

Таким образом, если вызов o.notifyAll () вызывает более одного ожидающего потока, а первый, возвращающийся из o.wait () , оставляет условие в ложном состоянии, тогда остальные темы, которые были разбужены, вернутся к ожиданию.

Другие советы

Ясно, notify пробуждает (любой) один поток в наборе ожидания, notifyAll пробуждает все потоки в ожидающем наборе.Следующее обсуждение должно рассеять любые сомнения. notifyAll следует использовать большую часть времени.Если вы не уверены, что использовать, то используйте notifyAll. Пожалуйста, ознакомьтесь с пояснением, приведенным ниже.

Прочтите очень внимательно и поймите.Пожалуйста, пришлите мне электронное письмо, если у вас есть какие-либо вопросы.

Посмотрите на producer / consumer (предполагается, что это класс ProducerConsumer с двумя методами).ОН СЛОМАН (потому что он использует notify) - да, это МОЖЕТ сработать - даже большую часть времени, но это также может привести к тупиковой ситуации - мы увидим, почему:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

ВО-ПЕРВЫХ,

Зачем нам нужен цикл while, окружающий ожидание?

Нам нужен while цикл на случай, если мы столкнемся с такой ситуацией:

Потребитель 1 (C1) вводит синхронизированный блок, и буфер пуст, поэтому C1 помещается в набор ожидания (через wait звонок).Потребитель 2 (C2) собирается ввести синхронизированный метод (в точке Y выше), но производитель P1 помещает объект в буфер и впоследствии вызывает notify.Единственным ожидающим потоком является C1, поэтому он пробужден и теперь пытается повторно получить блокировку объекта в точке X (выше).

Теперь C1 и C2 пытаются получить блокировку синхронизации.Один из них (недетерминированно) выбирается и входит в метод, другой блокируется (не ждет - но блокируется, пытаясь получить блокировку метода).Допустим, C2 получает блокировку первым.C1 все еще блокируется (пытается получить блокировку в X).C2 завершает выполнение метода и снимает блокировку.Теперь C1 получает блокировку.Знаешь что, нам повезло, что у нас есть while цикл, потому что C1 выполняет проверку цикла (guard) и не может удалить несуществующий элемент из буфера (C2 уже получил его!).Если бы у нас не было while, мы бы получили IndexArrayOutOfBoundsException поскольку C1 пытается удалить первый элемент из буфера!

СЕЙЧАС,

Хорошо, теперь зачем нам нужен notifyAll?

В приведенном выше примере производителя / потребителя, похоже, нам это сойдет с рук notify.Это кажется таким образом, потому что мы можем доказать, что охранники на подожди циклы для производителя и потребителя являются взаимоисключающими.То есть, похоже, что у нас не может быть потока, ожидающего в put метод, а также get метод, потому что для того, чтобы это было правдой, тогда должно быть правдой следующее:

buf.size() == 0 AND buf.size() == MAX_SIZE (предположим, что MAX_SIZE не равен 0)

ОДНАКО этого недостаточно, нам НУЖНО использовать notifyAll.Давайте посмотрим , почему ...

Предположим, у нас есть буфер размером 1 (чтобы упростить выполнение примера).Следующие шаги заводят нас в тупик.Обратите внимание, что ВСЯКИЙ раз, когда поток пробуждается с помощью notify, он может быть недетерминирован JVM - то есть любой ожидающий поток может быть разбужен.Также обратите внимание, что когда несколько потоков блокируют вход в метод (т.е.попытка получить блокировку), порядок получения может быть недетерминированным.Помните также, что поток может находиться только в одном из методов в любой момент времени - синхронизированные методы допускают выполнение только одного потока (т. е.удерживающий блокировку) любых (синхронизированных) методов в классе.Если происходит следующая последовательность событий - возникает взаимоблокировка:

ШАГ 1:
- P1 помещает 1 символ в буфер

ШАГ 2:
- Попытки P2 put - проверяет цикл ожидания - уже есть символ - ожидает

ШАГ 3:
- Попытки P3 put - проверяет цикл ожидания - уже есть символ - ожидает

ШАГ 4:
- C1 пытается получить 1 символ
- C2 пытается получить 1 символ - блокирует вход в get способ
- C3 пытается получить 1 символ - блокирует вход в get способ

ШАГ 5:
- C1 выполняет get метод - получает символ, вызывает notify, метод выхода
- Тот самый notify просыпается Р2
- НО C2 входит в метод до того, как P2 сможет (P2 должен повторно получить блокировку), поэтому P2 блокирует вход в put способ
- C2 проверяет цикл ожидания, в буфере больше нет символов, поэтому ожидает
- C3 вводит метод после C2, но перед P2 проверяет цикл ожидания, в буфере больше нет символов, поэтому ожидает

ШАГ 6:
- СЕЙЧАС:вас ждут P3, C2 и C3!
- Наконец, P2 получает блокировку, помещает символ в буфер, вызывает метод notify, завершает работу

ШАГ 7:
- Уведомление P2 будит P3 (помните, что любой поток может быть разбужен)
- P3 проверяет условие цикла ожидания, в буфере уже есть символ char, поэтому ожидает.
- БОЛЬШЕ НЕТ ПОТОКОВ ДЛЯ ВЫЗОВА NOTIFY и ТРИ ПОТОКА ПОСТОЯННО ПРИОСТАНОВЛЕНЫ!

РЕШЕНИЕ:Заменить notify с notifyAll в коде производителя / потребителя (выше).

Полезные различия:

  • Использование уведомить() если все ваши ожидающие потоки взаимозаменяемы (порядок их пробуждения не имеет значения), или если у вас есть только один ожидающий поток.Распространенным примером является пул потоков, используемый для выполнения заданий из очереди - при добавлении задания один из потоков получает уведомление о пробуждении, выполнении следующего задания и возвращении в спящий режим.

  • Использование Уведомлять всех () для других случаев, когда ожидающие потоки могут иметь разные цели и должны иметь возможность выполняться одновременно.Примером может служить операция обслуживания общего ресурса, когда несколько потоков ожидают завершения операции, прежде чем получить доступ к ресурсу.

Я думаю, что это зависит от того, как ресурсы производятся и потребляются. Если одновременно доступно 5 рабочих объектов и у вас есть 5 потребительских объектов, имеет смысл разбудить все потоки с помощью notifyAll (), чтобы каждый мог обработать 1 рабочий объект.

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

Я нашел отличное объяснение здесь . Короче говоря:

  

Обычно используется метод notify ()   для пулов ресурсов , где   произвольное число «потребителей»;   или "рабочие" что берут ресурсы, но   когда ресурс добавлен в пул,   только один из ожидающих потребителей или   рабочие могут иметь дело с этим.   Метод notifyAll () фактически используется в   большинство других случаев. Строго говоря, это   необходимо уведомить официантов о   состояние, которое может позволить несколько   официанты, чтобы продолжить. Но это часто   трудно узнать. Так что в целом   правило, , если у вас нет конкретного   логика для использования notify (), то вы   вероятно, следует использовать notifyAll () ,   потому что это часто трудно узнать   какие именно темы будут ждать   на конкретный объект и почему.

Обратите внимание, что с утилитами параллелизма у вас также есть выбор между signal () и signalAll () , так как эти методы вызываются там. Таким образом, вопрос остается в силе даже с java.util.concurrent .

Даг Ли поднимает интересный вопрос в своей известной книге : если notify () и Thread.interrupt () происходят одновременно, уведомление может фактически потеряться. Если это может произойти и имеет драматические последствия, notifyAll () - более безопасный выбор, даже если вы платите за издержки (большую часть времени просыпаете слишком много потоков).

От Джошуа Блока, самого Гуру Java в Effective Java 2nd edition:

" Элемент 69. Предпочитают утилиты параллелизма ждать и уведомлять ".

Вот пример. Запустить его. Затем измените один из notifyAll () на notify () и посмотрите, что произойдет.

ProducerConsumerExample class

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Класс Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Потребительский класс

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Класс продюсера

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

Краткое резюме:

Всегда предпочитаю Уведомлять всех () закончился уведомить() если только у вас нет массово параллельного приложения, где большое количество потоков делают одно и то же.

Объяснение:

уведомить() [...] пробуждает единственный поток.Потому что уведомить() не позволяют указать поток, проснувшись, он полезен только в массово-параллельных приложений — это есть программы с большим количеством потоков, все подобные хлопоты.В таком приложении вам все равно, какой поток будет разбужен.

Источник: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Сравнить уведомить() с Уведомлять всех () в вышеописанной ситуации:массово параллельное приложение, в котором потоки делают одно и то же.Если ты позвонишь Уведомлять всех () в таком случае, Уведомлять всех () вызовет пробуждение (т.е.планирование) огромного количества потоков, многие из которых излишни (поскольку фактически может выполняться только один поток, а именно поток, которому будет предоставлен монитор для объекта ждать(), уведомить(), или Уведомлять всех () был вызван), следовательно, напрасно тратя вычислительные ресурсы.

Таким образом, если у вас нет приложения, в котором огромное количество потоков выполняют одно и то же одновременно, предпочтите Уведомлять всех () закончился уведомить().Почему?Потому что, как уже ответили другие пользователи на этом форуме, уведомить()

пробуждает единственный поток, который ожидает на мониторе этого объекта.[...] Выбор таков произвольный и происходит по усмотрению реализации.

Источник:Java SE8 API (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Представьте, что у вас есть приложение-производитель-потребитель, к которому потребители готовы (т. е. ждать() ing) к потреблению производители готовы (т.е. ждать() ing) для производства, и очередь товаров (подлежащих производству / потреблению) пуста.В таком случае, уведомить() могут проснуться только потребители, но никогда производители, потому что выбор того, кого разбудить, таков произвольный.Цикл производитель-потребитель не добился бы никакого прогресса, хотя производители и потребители готовы производить и потреблять соответственно.Вместо этого потребитель просыпается (т.е.покидая ждать() status), не выводит элемент из очереди, потому что он пуст, и уведомить() попросите другого потребителя продолжить.

В отличие, Уведомлять всех () пробуждает как производителей, так и потребителей.Выбор того, кто будет назначен, зависит от планировщика.Конечно, в зависимости от реализации планировщика, планировщик также может планировать только потребителей (напримересли вы назначаете потокам-потребителям очень высокий приоритет).Однако здесь предполагается, что опасность того, что планировщик будет планировать только потребителей, ниже, чем опасность того, что JVM будет пробуждать только потребителей, потому что любой разумно реализованный планировщик не будет просто произвольный решения.Скорее всего, большинство реализаций планировщика прилагают по крайней мере некоторые усилия, чтобы предотвратить "голодание".

Я очень удивлен, что никто не упомянул о печально известной проблеме "потерянного пробуждения" (погуглите ее).

В основном:

  1. если у вас есть несколько потоков, ожидающих выполнения одного и того же условия, и,
  2. несколько потоков, которые могут заставить вас перейти из состояния A в состояние B и,
  3. несколько потоков, которые могут заставить вас перейти из состояния B в состояние A (обычно те же потоки, что и в 1.) и,
  4. переход из состояния A в B должен уведомлять потоки в 1.

ТОГДА вам следует использовать notifyAll, если только у вас нет доказуемых гарантий того, что потерянные пробуждения невозможны.

Распространенным примером является параллельная очередь FIFO, где:несколько очередей (1.и 3.выше) может перевести вашу очередь из пустой в непустую несколько отмен (2.выше) может ожидать выполнения условия "очередь не пуста" empty -> non-empty должен уведомлять об отмене очереди

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

Эта проблема, возможно, сравнима с проблемой взаимоблокировки.

Надеюсь, это прояснит некоторые сомнения.

notify () : метод notify () пробуждает один поток, ожидающий для блокировки (первый поток, который вызвал wait () для этой блокировки).

notifyAll () : метод notifyAll () пробуждает все потоки, ожидающие блокировки; JVM выбирает один из потоков из списка потоков, ожидающих блокировки, и просыпается этот поток вверх.

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

На первый взгляд кажется, что хорошей идеей будет просто вызвать notify () для пробуждения одного потока; может показаться ненужным разбудить все темы. Однако проблема с notify () заключается в том, что проснувшийся поток может оказаться неподходящим для пробуждения (поток может ожидать какого-то другого условия или условие все еще не удовлетворено для этот поток и т. д.). В этом случае функция notify () может быть потеряна, и никакой другой поток не сможет проснуться, что приведет к типу тупика (уведомление потеряно, а все остальные потоки ожидают уведомления & # 8212; навсегда) .

Чтобы избежать этой проблемы , всегда лучше вызывать notifyAll (), когда более одного потока ожидает блокировки (или более одного условия, при котором выполняется ожидание). Метод notifyAll () пробуждает все потоки, поэтому он не очень эффективен. однако эта потеря производительности незначительна в реальных приложениях.

notify () разбудит один поток, а notifyAll () разбудит всех. Насколько я знаю, нет никакого среднего уровня. Но если вы не уверены, что notify () сделает с вашими потоками, используйте notifyAll () . Работает как шарм каждый раз.

Все вышеприведенные ответы верны, насколько я могу судить, поэтому я собираюсь рассказать вам кое-что еще. Для производственного кода вы действительно должны использовать классы в java.util.concurrent. В области параллелизма в Java они мало что могут для вас сделать.

Вот более простое объяснение:

Вы правы в том, что независимо от того, используете ли вы notify () или notifyAll (), немедленный результат заключается в том, что ровно один другой поток получит монитор и начнет выполнение. (Предполагая, что некоторые потоки были фактически заблокированы в wait () для этого объекта, другие не связанные потоки не впитывают все доступные ядра и т. Д.) Влияние наступит позже.

Предположим, что поток A, B и C ожидали этого объекта, а поток A получает монитор. Разница заключается в том, что происходит, когда А выпускает монитор. Если вы использовали notify (), то B и C по-прежнему блокируются в wait (): они не ждут на мониторе, они ожидают уведомления. Когда A отпускает монитор, B и C все еще сидят там, ожидая уведомления ().

Если вы использовали notifyAll (), то B и C продвинулись вперед над "ожиданием уведомления" " состояние и оба ждут, чтобы приобрести монитор. Когда A освобождает монитор, B или C получат его (при условии, что другие потоки не конкурируют за этот монитор) и начнут выполнение.

Есть три состояния для потока. <Ол>

  • WAIT - поток не использует цикл ЦП
  • BLOCKED - поток блокируется при попытке получить монитор. Возможно, он по-прежнему использует циклы процессора
  • RUNNING - поток запущен.
  • Теперь, когда вызывается notify (), JVM выбирает один поток и переводит его в состояние BLOCKED и, следовательно, в состояние RUNNING, поскольку нет конкуренции за объект монитора.

    Когда вызывается notifyAll (), JVM выбирает все потоки и переводит их в состояние BLOCKED. Все эти потоки получат блокировку объекта в приоритетном порядке. Поток, который может получить монитор первым, сможет сначала перейти в состояние RUNNING и т. Д.

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

    Зачем использовать notifyAll, даже если каждый продукт предназначен для одного потребителя?

    Рассмотрим производителей и потребителей в упрощенном виде следующим образом.

    Производитель:

    while (!empty) {
       wait() // on full
    }
    put()
    notify()
    

    Потребитель:

    while (empty) {
       wait() // on empty
    }
    take()
    notify()
    

    Предположим, что 2 производителя и 2 потребителя совместно используют буфер размером 1.На следующем рисунке показан сценарий, ведущий к тупиковая ситуация, чего можно было бы избежать , если бы все потоки использовались Уведомлять всех.

    Каждое уведомление помечается пробуждаемым потоком.

    deadlock due to notify

    notify () позволяет писать более эффективный код, чем notifyAll () .

    Рассмотрим следующий фрагмент кода, который выполняется из нескольких параллельных потоков:

    synchronized(this) {
        while(busy) // a loop is necessary here
            wait();
        busy = true;
    }
    ...
    synchronized(this) {
        busy = false;
        notifyAll();
    }
    

    Это можно сделать более эффективным с помощью notify () :

    synchronized(this) {
        if(busy)   // replaced the loop with a condition which is evaluated only once
            wait();
        busy = true;
    }
    ...
    synchronized(this) {
        busy = false;
        notify();
    }
    

    В случае, если у вас большое количество потоков, или если условие цикла ожидания требует больших затрат, notify () будет значительно быстрее, чем notifyAll () . Например, если у вас 1000 потоков, то 999 потоков будут пробуждены и оценены после первого notifyAll () , затем 998, затем 997 и так далее. Напротив, с решением notify () будет пробужден только один поток.

    Используйте notifyAll () , когда вам нужно выбрать, какой поток будет выполнять следующую работу:

    synchronized(this) {
        while(idx != last+1)  // wait until it's my turn
            wait();
    }
    ...
    synchronized(this) {
        last = idx;
        notifyAll();
    }
    

    Наконец, важно понимать, что в случае notifyAll () код внутри блоков synchronized , которые были разбужены, будет выполняться последовательно, а не все сразу. Допустим, в приведенном выше примере три потока ожидают, а четвертый поток вызывает notifyAll () . Будут пробуждены все три потока, но только один начнет выполнение и проверит состояние цикла while . Если условие имеет значение true , он снова вызовет wait () , и только тогда второй поток начнет выполняться и проверит свой цикл while состояние и т. д.

    Взято из Блог об эффективной Java:

    The notifyAll method should generally be used in preference to notify. 
    
    If notify is used, great care must be taken to ensure liveness.
    

    Итак, что я понимаю, так это (из вышеупомянутого блога, комментарий "Yann TM" к принятый ответ и Java Документы):

    • уведомить() :JVM пробуждает один из ожидающих потоков для этого объекта.Выбор темы производится произвольно, без какой-либо справедливости.Таким образом, один и тот же поток может пробуждаться снова и снова.Таким образом, состояние системы меняется, но реального прогресса не наблюдается.Таким образом, создавая живой замок.
    • Уведомлять всех () :JVM пробуждает все потоки, а затем все потоки соревнуются за блокировку этого объекта.Теперь планировщик процессора выбирает поток, который получает блокировку этого объекта.Этот процесс отбора был бы намного лучше, чем отбор с помощью JVM.Таким образом, обеспечивается живучесть.

    Взгляните на код, размещенный @xagyg.

    Предположим, что два разных потока ожидают двух разных условий:
    первый поток ожидает buf.size ()! = MAX_SIZE , а второй поток ожидает buf.size ( )! = 0 .

    Предположим, в какой-то момент buf.size () не равно 0 . JVM вызывает notify () вместо notifyAll () , и первый поток уведомляется (а не второй).

    Первый поток просыпается, проверяет buf.size () , который может возвращать MAX_SIZE , и возвращается к ожиданию. Второй поток не проснулся, продолжает ждать и не вызывает get () .

    notify () вызывает первый поток, вызвавший wait () для того же объекта.

    notifyAll () активирует все потоки, вызвавшие wait () для одного и того же объекта.

    Поток с наивысшим приоритетом будет запущен первым.

    Я хотел бы упомянуть, что объясняется в Java Concurrency на практике:

    Первый пункт, будь то Notify или NotifyAll?

    It will be NotifyAll, and reason is that it will save from signall hijacking.
    
      

    Если два потока A и B ожидают разных предикатов условия   вызывается очередь с таким же условием и уведомлять, тогда это до JVM   какой поток JVM уведомит.

         

    Теперь, если уведомление предназначено для потока A, а JVM уведомляет поток B, тогда   поток B проснется и увидит, что это уведомление бесполезно, поэтому   это будет ждать снова. И Тема А никогда не узнает об этом   пропущенный сигнал, и кто-то похитил его уведомление.

         

    Таким образом, вызов notifyAll решит эту проблему, но снова   влияние на производительность, поскольку он будет уведомлять все потоки и все потоки будут   конкурировать за ту же блокировку, и это будет включать переключение контекста и, следовательно,   нагрузка на процессор. Но мы должны заботиться о производительности, только если она   вести себя правильно, если это поведение само по себе не правильно, то   производительность бесполезна.

    Эту проблему можно решить с помощью объекта условия явной блокировки Lock, представленного в jdk 5, поскольку он обеспечивает различное ожидание для каждого предиката условия. Здесь он будет вести себя правильно и не будет проблем с производительностью, так как вызовет сигнал и убедится, что только один поток ожидает этого условия

    notify уведомит только один поток, находящийся в состоянии ожидания, в то время как notify all уведомит все потоки в состоянии ожидания, теперь все уведомленные потоки и все заблокированные потоки имеют право на блокировку, из которой только один получит замок и все остальные (включая тех, кто ранее находился в состоянии ожидания) будут в заблокированном состоянии.

    notify () - выбирает случайный поток из набора ожидания объекта и переводит его в состояние BLOCKED . Остальные потоки в наборе ожидания объекта все еще находятся в состоянии WAITING .

    notifyAll () - переводит все потоки из набора ожидания объекта в состояние BLOCKED . После использования notifyAll () в наборе ожидания общего объекта не осталось потоков, поскольку все они теперь находятся в состоянии BLOCKED , а не в WAITING состояние.

    BLOCKED - заблокирован для получения блокировки. WAITING - ожидает уведомления (или заблокирован для завершения соединения).

    Подводя итог прекрасным подробным объяснениям, приведенным выше, и самым простым способом, который я могу себе представить, это связано с ограничениями встроенного монитора JVM, который 1) приобретается на всем блоке синхронизации (блоке или объекте) и 2) не дискриминирует конкретное условие, которое ожидает / уведомляет / о.

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

    Напротив, notifyAll () позволяет всем ожидающим потокам в конечном итоге повторно захватывать блокировку и проверять их соответствующее состояние, тем самым, в конечном итоге, позволяя добиться прогресса.

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

    Когда вы вызываете wait () для "объекта" (ожидая, что блокировка объекта получена), внутренне это снимет блокировку с этого объекта и поможет другим потокам заблокировать этот "объект" в В этом сценарии будет более 1 потока, ожидающего «ресурс / объект» (учитывая, что другие потоки также выдавали ожидание для того же вышеупомянутого объекта, и вниз по пути будет поток, который заполняет ресурс / объект и вызывает уведомление / notifyAll).

    Здесь, когда вы отправляете уведомление об одном и том же объекте (с той же / другой стороны процесса / кода), это освобождает заблокированный и ожидающий один поток (не все ожидающие потоки - этот освобожденный поток будет выбран JVM Thread Scheduler и весь процесс получения блокировки на объекте такой же, как и в обычном режиме.

    Если у вас есть только один поток, который будет совместно использовать / работать с этим объектом, то можно использовать метод notify () в своей реализации wait-notify.

    если вы находитесь в ситуации, когда более одного потока читает и записывает ресурсы / объекты на основе вашей бизнес-логики, тогда вам следует обратиться к notifyAll ()

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

    Хотя есть несколько убедительных ответов выше, я удивлен тем количеством недоразумений и недоразумений, которые я прочитал. Это, вероятно, подтверждает идею о том, что следует как можно больше использовать java.util.concurrent вместо того, чтобы пытаться писать собственный неработающий параллельный код. Вернемся к вопросу: подведем итог: сегодня лучше избегать уведомления () во всех ситуациях из-за проблемы с пробуждением. Любой, кто не понимает этого, не должен иметь права писать критически важный код параллелизма. Если вас беспокоит проблема скотоводства, один из безопасных способов добиться пробуждения одной нитью за раз: 1. Создайте явную очередь ожидания для ожидающих потоков; 2. Пусть каждый поток в очереди ожидает своего предшественника; 3. Пусть каждый поток вызывает notifyAll () после завершения. Или вы можете использовать Java.util.concurrent. *, Который уже реализовал это.

    Пробуждение всех здесь не имеет большого значения. ждать уведомления и уведомления, все они ставятся после владения монитором объекта. Если поток находится в стадии ожидания и вызывается notify, этот поток займет блокировку, и никакой другой поток в этой точке не сможет принять эту блокировку. Таким образом, одновременный доступ не может иметь место вообще. Насколько мне известно, любой вызов wait и notifyall можно сделать только после взятия блокировки на объекте. Поправь меня, если я ошибаюсь.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top