Java: notify () против notifyAll () снова и снова - PullRequest
347 голосов
/ 31 августа 2008

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

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

Что такое полезная разница между notify () и notifyAll () тогда? Я что-то упустил?

Ответы [ 26 ]

4 голосов
/ 25 июня 2013

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

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

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

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

4 голосов
/ 08 января 2017

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

  1. WAIT - поток не использует цикл ЦП
  2. BLOCKED - поток блокируется при попытке получить монитор. Возможно, он все еще использует циклы ЦП
  3. RUNNING - поток запущен.

Теперь, когда вызывается notify (), JVM выбирает один поток и переводит его в состояние BLOCKED и, следовательно, в состояние RUNNING, поскольку для объекта монитора нет конкуренции.

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

4 голосов
/ 25 мая 2017

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

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

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

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

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

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

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

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

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

deadlock due to notify

4 голосов
/ 31 августа 2008

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

4 голосов
/ 31 августа 2008

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

3 голосов
/ 26 сентября 2017

Взято из блога на эффективной 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 документация ):

  • notify (): JVM пробуждает один из ожидающих потоков на этом объекте. Выбор темы производится произвольно без справедливости. Так что одну и ту же нить можно разбудить снова и снова. Таким образом, состояние системы изменяется, но никакого реального прогресса не происходит. Таким образом создавая livelock .
  • notifyAll (): JVM пробуждает все потоки, а затем все потоки борются за блокировку этого объекта. Теперь планировщик ЦП выбирает поток, который получает блокировку для этого объекта. Этот процесс отбора будет намного лучше, чем отбор со стороны JVM. Таким образом, обеспечение жизнедеятельности.
3 голосов
/ 18 июня 2013

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 и т. Д.

2 голосов
/ 27 марта 2012

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

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

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

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

1 голос
/ 05 декабря 2011

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

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

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

1 голос
/ 25 октября 2015

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

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

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

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