Если поискать в Google "разница между notify() и notifyAll()", появится множество объяснений (не говоря уже о параграфах javadoc). Все сводится к количеству пробуждаемых ожидающих потоков: один в notify() и все в notifyAll().
Однако (если я правильно понимаю разницу между этими методами), для дальнейшего получения монитора всегда выбирается только один поток; в первом случае тот, который выбран виртуальной машиной, во втором случае тот, который выбран планировщиком системного потока. Точные процедуры выбора для них обоих (в общем случае) программисту неизвестны.
В чем же тогда разница полезный между уведомлять() и notifyAll ()? Я что-то пропустил?
Я не согласен с Питером. Библиотека параллелизма реализована на Java, и каждый раз, когда вы вызываете lock (), unlock () и т. д., Выполняется много Java-кода. Вы можете выстрелить себе в ногу, используя библиотеку параллелизма вместо старого доброго synchronized, за исключением определенные, довольно редкие варианты использования.
Ключевое недоразумение, похоже, заключается в следующем: ... для дальнейшего сбора данных всегда выбирается только один поток; в первом случае тот, который выбран виртуальной машиной, во втором случае тот, который выбран планировщиком системного потока. Подразумевается, что они по сути одинаковы. Хотя описанное поведение является правильным, чего не хватает в случае notifyAll(), _другие потоки после первого остаются активными и будут получать данные монитора один за другим. В случае notify ни один из других потоков даже не разбужен. Так что функционально они очень разные!
1) Если много потоков ожидают объекта, а notify () вызывается только один раз для этого объекта. За исключением одного из ожидающих потоков, остальные потоки ждут вечно? 2) Если используется notify (), только один из многих ожидающих потоков начинает выполняться. Если используется notifyall (), все ожидающие потоки уведомляются, но только один из них начинает выполняться, так что в чем здесь notifyall ()?
@ChetanGowda Уведомление всех потоков vs Уведомление только об одном произвольном потоке на самом деле имеет существенное различие, пока это, казалось бы, тонкое, но важное отличие не поразит нас. Когда вы notify () только 1 поток, все остальные потоки будут находиться в состоянии ожидания, пока он не получит явное уведомление / сигнал. Уведомляя всех, все потоки будут выполняться и завершаться в некотором порядке один за другим без какого-либо дополнительного уведомления - здесь мы должны сказать, что потоки - это blocked, а не waiting. Когда blocked его выполнение временно приостанавливается, пока другой поток не окажется внутри блока sync. .




notify() разбудит один поток, а notifyAll() разбудит все. Насколько я знаю, золотой середины нет. Но если вы не уверены, что notify() сделает с вашими потоками, используйте notifyAll(). Каждый раз работает как шарм.
Я думаю, это зависит от того, как ресурсы производятся и потребляются. Если 5 рабочих объектов доступны одновременно и у вас есть 5 объектов-потребителей, имеет смысл разбудить все потоки с помощью notifyAll (), чтобы каждый мог обработать 1 рабочий объект.
Если у вас есть только один рабочий объект, какой смысл пробуждать все потребительские объекты для гонки за этим одним объектом? Первый, проверяющий доступную работу, получит ее, а все остальные потоки проверят и обнаружат, что им нечего делать.
Нашел отличное объяснение здесь. Короче:
The notify() method is generally used for resource pools, where there are an arbitrary number of "consumers" or "workers" that take resources, but when a resource is added to the pool, only one of the waiting consumers or workers can deal with it. The notifyAll() method is actually used in most other cases. Strictly, it is required to notify waiters of a condition that could allow multiple waiters to proceed. But this is often difficult to know. So as a general rule, if you have no particular logic for using notify(), then you should probably use notifyAll(), because it is often difficult to know exactly what threads will be waiting on a particular object and why.
However (if I do understand the difference between these methods right), only one thread is always selected for further monitor acquisition.
Это не так. o.notifyAll() пробуждает все потоков, заблокированных в вызовах o.wait(). Потокам разрешено возвращаться из o.wait() только один за другим, но каждый буду получает свою очередь.
Проще говоря, это зависит от того, почему ваши потоки ждут уведомления. Вы хотите сообщить одному из ожидающих потоков, что что-то произошло, или вы хотите рассказать всем им одновременно?
В некоторых случаях все ожидающие потоки могут предпринять полезные действия после завершения ожидания. Примером может быть набор потоков, ожидающих завершения определенной задачи; после завершения задачи все ожидающие потоки могут продолжить свою работу. В таком случае вы должны использовать notifyAll () для одновременного пробуждения всех ожидающих потоков.
Другой случай, например взаимоисключающая блокировка, только один из ожидающих потоков может сделать что-то полезное после уведомления (в этом случае получить блокировку). В таком случае лучше использовать уведомлять(). При правильной реализации вы мог также используете notifyAll () в этой ситуации, но вы излишне будите потоки, которые все равно ничего не могут сделать.
Во многих случаях код ожидания условия будет записан в виде цикла:
synchronized(o) {
while (! IsConditionTrue()) {
o.wait();
}
DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}
Таким образом, если вызов o.notifyAll() пробуждает более одного ожидающего потока, а тот, который первым вернулся из o.wait(), оставляет условие в ложном состоянии, тогда другие потоки, которые были пробуждены, вернутся в режим ожидания.
если вы уведомляете только один поток, но несколько ожидают объекта, как виртуальная машина определяет, какой из них уведомить?
Я не могу сказать наверняка о спецификации Java, но в целом вам следует избегать предположений о таких деталях. Я думаю, вы можете предположить, что виртуальная машина будет делать это разумно и справедливо.
Лидман серьезно ошибается, спецификация Java прямо заявляет, что notify () не гарантирует справедливости. т.е. каждый вызов уведомления может снова разбудить тот же поток (очередь потоков в мониторе НЕ ЯВНА или FIFO). Однако планировщик гарантированно будет честным. Вот почему в большинстве случаев, когда у вас более двух потоков, вы должны предпочесть notifyAll.
@YannTM Я полностью за конструктивную критику, но я думаю, что ваш тон немного несправедлив. Я прямо сказал «не могу сказать наверняка» и «я думаю». Успокойтесь, вы когда-нибудь писали что-то семь лет назад, что было не на 100% правильно?
Проблема в том, что это общепринятый ответ, а не вопрос личной гордости. Если вы знаете, что ошибались сейчас, отредактируйте свой ответ, сказав это, и укажите, например, xagyg педагогический и правильный ответ ниже.
@Liedman Ваш ответ звучит хорошо, но в связи с этим возникает другой вопрос. Если я сделаю notifyall (), и он разбудит весь поток, который находился в очереди ожидания, вы соглашаетесь с тем, что только один поток может получить доступ к блокировке и войти в этот синхронизированный блок или метод, и на основе этого, естественно, другой поток не получит шансов войти в заблокированный раздел, поэтому они попадают в состояние ожидания. Разве вы не думаете, что в конечном итоге это то же самое? Я просто пытаюсь прояснить это. На данный момент везде, где говорится, что notify пробуждает одного и notifyAll пробуждает всех, но в обоих случаях только один поток может получить блокировки. Так в чем разница?
Спасибо за объяснение (в этом случае получить блокировку), я думаю, главное отличие.
@MrA Разница в том, что в notifyAll () другие потоки не переходят в состояние waiting, он переходит в состояние blocked. waiting - вы «ждете» уведомления от другого потока, и это связь между потоками, blocked - как только поток завершает критический раздел / синхронизированный блок, планировщик ОС решает запустить следующий поток, и все потоки видят его завершение в некотором порядке синхронизированным образом (если вы не заставите другие потоки снова перейти в состояние waiting).
@MrA В notify () потоки могут в конечном итоге бесконечно ждать уведомления, потому что уведомляется какой-то поток, который не может добиться какого-либо прогресса вместо потока, который может прогрессировать, и если есть вероятность, что это может произойти в вашей системе / реализации, и если вы обнаружите, что ваше приложение может не работать или работать должным образом, если это произойдет, тогда notifyAll() является правильной конструкцией.
@YannTM где в спецификации вы нашли утверждение «планировщик гарантированно работает честно»? Такой гарантии справедливости нет, поэтому использовать notifyAll только для этой цели бессмысленно.
Полезные отличия:
Используйте уведомлять(), если все ваши ожидающие потоки взаимозаменяемы (порядок их пробуждения не имеет значения) или если у вас когда-либо был только один ожидающий поток. Типичным примером является пул потоков, используемый для выполнения заданий из очереди - когда задание добавлено, один из потоков уведомляется о пробуждении, выполнении следующего задания и переходе в спящий режим.
Используйте notifyAll () для других случаев, когда ожидающие потоки могут иметь разные цели и должны иметь возможность работать одновременно. Примером является операция обслуживания общего ресурса, когда несколько потоков ожидают завершения операции, прежде чем получить доступ к ресурсу.
Насколько я могу судить, все приведенные выше ответы верны, поэтому я расскажу вам кое-что еще. Для производственного кода вам действительно следует использовать классы в java.util.concurrent. Они мало что могут сделать для вас в области параллелизма в java.
От Джошуа Блоха, самого Java-гуру в Effective Java 2nd edition:
«Правило 69: Предпочитайте утилиты параллелизма ждать и уведомлять».
Почему более важен, чем источник.
@Pacerier Хорошо сказано. Мне было бы больше интересно выяснить причины. Одна из возможных причин может заключаться в том, что ожидание и уведомление в классе объектов основаны на неявной переменной условия. Итак, в стандартном примере производителя и потребителя ... и производитель, и потребитель будут ждать в одном и том же состоянии, что может привести к тупику, как объяснил xagyg в своем ответе. Таким образом, лучший подход - использовать 2 условные переменные, как описано в docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks /….
Обратите внимание, что с утилитами параллелизма у вас также есть выбор между signal() и signalAll(), поскольку эти методы там вызываются. Так что вопрос остается актуальным даже с java.util.concurrent.
Дуг Ли поднимает интересный момент в своем известная книга: если notify() и Thread.interrupt() происходят одновременно, уведомление может фактически потеряться. Если это может произойти и имеет серьезные последствия, notifyAll() - более безопасный выбор, даже если вы платите накладные расходы (большую часть времени будите слишком много потоков).
Пробуждение всех здесь не имеет большого значения. wait notify и notifyall, все они ставятся после владения монитором объекта. Если поток находится в стадии ожидания и вызывается уведомление, этот поток принимает блокировку, и никакой другой поток в этот момент не может принять эту блокировку. Таким образом, одновременный доступ вообще невозможен. Насколько мне известно, любой вызов wait notify и notifyall может быть выполнен только после блокировки объекта. Поправьте меня, если я ошибаюсь.
Вот пример. Запустить его. Затем измените один из notifyAll () на notify () и посмотрите, что произойдет.
ProducerConsumerExample класс
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) { }
}
}
}
Очевидно, что notify пробуждает (любой) один поток в наборе ожидания, notifyAll пробуждает все потоки в наборе ожидания. Следующее обсуждение должно развеять любые сомнения. notifyAll следует использовать большую часть времени. Если вы не уверены, что использовать, используйте notifyAll. См. Пояснения ниже.
Внимательно прочтите и поймите. Если у вас есть вопросы, пришлите мне письмо по электронной почте.
Посмотрите на производителя / потребителя (предположение - это класс 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 выполняет проверку цикла (защита) и не может удалить несуществующий элемент из буфера (C2 уже получил его!). Если бы у нас не было while, мы получили бы IndexArrayOutOfBoundsException, поскольку C1 пытается удалить первый элемент из буфера!
СЕЙЧАС ЖЕ,
Хорошо, а зачем нам notifyAll?
В приведенном выше примере производителя / потребителя похоже, что мы можем обойтись без notify. Так кажется, потому что мы можем доказать, что средства защиты в циклах ждать для производителя и потребителя являются взаимоисключающими. То есть, похоже, что у нас не может быть потока, ожидающего в методе put, а также в методе get, потому что для этого должно быть верно следующее:
buf.size() == 0 AND buf.size() == MAX_SIZE (предположим, что MAX_SIZE не 0)
ОДНАКО этого недостаточно, нам НЕОБХОДИМО использовать notifyAll. Посмотрим, почему ...
Предположим, у нас есть буфер размером 1 (чтобы упростить пример). Следующие шаги приводят нас в тупик. Обратите внимание, что ЛЮБОЙ РАЗ поток будит с уведомлением, он может быть недетерминированно выбран JVM - то есть любой ожидающий поток может быть разбужен. Также обратите внимание, что когда несколько потоков блокируют вход в метод (т.е. пытаются получить блокировку), порядок получения может быть недетерминированным. Помните также, что поток может быть только в одном из методов в любой момент времени - синхронизированные методы позволяют только одному потоку выполнять (т.е. удерживать блокировку) любых (синхронизированных) методов в классе. Если происходит следующая последовательность событий - возникает тупик:
ШАГ 1:
- P1 помещает в буфер 1 символ
ШАГ 2:
- P2 пытается put - проверяет цикл ожидания - уже символ - ждет
ШАГ 3:
- P3 пытается put - проверяет цикл ожидания - уже символ - ждет
ШАГ 4:
- C1 пытается получить 1 символ
.
- C2 пытается получить 1 символ - блокируется при входе в метод get
- C3 пытается получить 1 символ - блокируется при входе в метод get
ШАГ 5:
- C1 выполняет метод get - получает символ, вызывает notify, выходит из метода
- notify пробуждает P2
- НО, C2 входит в метод до того, как P2 сможет (P2 должен повторно получить блокировку), поэтому P2 блокируется при входе в метод put
- C2 проверяет цикл ожидания, в буфере больше нет символов, поэтому waits
- C3 входит в метод после C2, но перед P2, проверяет цикл ожидания, больше нет символов в буфере, поэтому ждет
ШАГ 6:
- СЕЙЧАС: ждут P3, C2 и C3!
- Наконец P2 получает блокировку, помещает символ в буфер, вызывает notify, выходит из метода
ШАГ 7:
- Уведомление P2 будит P3 (помните, что любой поток может быть разбужен)
- P3 проверяет условие цикла ожидания, в буфере уже есть символ, поэтому ждет.
- НЕТ БОЛЬШЕ НИТЕЙ ДЛЯ УВЕДОМЛЕНИЯ И ТРИ НИТКИ НАСТОЯЩИМ ПРИОСТАНОВЛЕННЫМИ!
РЕШЕНИЕ: Замените notify на notifyAll в коде производителя / потребителя (см. Выше).
Есть альтернатива: если пробужденный поток (P3 в вашем примере) не обнаруживает интересующее его условие, он снова вызывает notify(). Таким образом, notify() вызывается столько раз, сколько необходимо, пока один поток не сможет продолжить работу.
finnw - P3 должен повторно проверить условие, потому что notify заставляет P3 (выбранный поток в этом примере) продолжить работу с точки, которую он ожидал (то есть внутри цикла while). Существуют и другие примеры, которые не вызывают тупик, однако в этом случае использование notify не гарантирует отсутствие тупиковой блокировки кода. Использование notifyAll делает.
@xagyg, условие необходимо повторно проверять до тех пор, пока в буфере не появится символ, но это может быть либо тот же поток (в цикле ожидания / проверки), либо цикл потоков, уведомляющих друг друга, пока в буфере не будет найден символ .
Позвольте мне посмотреть, понимаю ли я это: есть 2 места, где поток может застрять: ожидание в wait () или попытка получить блокировку после того, как он просыпается из wait (). После notifyAll () все потоки просыпаются, но только один сможет повторно получить блокировку. После notify () просыпается только 1 поток, а остальные продолжают ждать другого notify ().
Привет @xagyg! Приведет ли вызов notifyAll () к тому, что несколько ожидающих потоков одновременно будут проверять условие while () ... и, следовательно, есть вероятность, что до того, как while будет сохранено, 2 потока уже вышли из него, вызывая исключение outOfBound? . Также я изменил свой небольшой код на основе вашего решения, буду признателен, если вы сможете его просмотреть: codereview.stackexchange.com/questions/3157/…
@xagyg, Пример последовательности неполный. «NO MORE THREADS TO CALL NOTIFY» неверно, поскольку ожидают только p3, c2 и c3 - p1, p2 и c1 все еще могут вызывать put и get и уведомлять остальных. Чтобы завершить последовательность взаимоблокировки: p1 и p2 должны попытаться установить и подождать, потому что буфер заполнен. Затем c1 должен получить символ и уведомить c2. Затем c1 и c2 должны попытаться получить еще один символ и подождать, потому что буфер пуст. Только тогда у вас будет 3 производителя и 3 потребителя в ожидании, и никто не сможет уведомить вас.
@marcus Очень близко. С помощью notifyAll каждый поток повторно захватит блокировку (по одному), но обратите внимание, что после того, как один поток повторно получил блокировку и выполнил метод (а затем вышел) ... следующий поток повторно захватывает блокировку, проверяет «пока» и вернется в режим ожидания (конечно, в зависимости от состояния). Итак, notify будит один поток - как вы правильно заявили. notifyAll будит все потоки, и каждый поток повторно захватывает блокировку по одному - проверяет условие «while» и либо выполняет метод, либо снова «ждет».
@eran ваше описание неверно. Во-первых, p1 уже завершен. Я не пойду дальше.
@finnw Думаю, что нет. Удачи в программировании. Тем не менее, спасибо за время, которое вы потратили на это. Если кому-то нужна дополнительная информация по этому поводу, я могу прислать очень подробную формальную спецификацию того, как работает многопоточность в Java. Кажется, есть много неправильных представлений об этом.
@xagyg, вы говорите о сценарии, в котором у каждого производителя есть только один единственный символ для хранения? Если да, то ваш пример правильный, но не очень интересный ИМО. С помощью дополнительных шагов, которые я предлагаю, вы можете заблокировать ту же систему, но с неограниченным количеством входных данных - именно так такие шаблоны обычно используются в реальности.
Использование notify vs notifyall во втором примере не вызывает взаимоблокировки. Это нить голодания
@Ovidiu, это зависит от вашего определения. Тип тупика, описанный в примере, можно было бы более конкретно назвать «бездействием» (в честь Дуга Ли). Другой распространенный тип тупика - «смертельное объятие» (проблема, отличная от той, что в примере). Обычно люди используют термин «тупик» для обозначения неудач жизнеспособности. Я оставляю за собой термин «голодание» для ситуаций, связанных с конфликтом (и, в частности, того, как JVM может вызвать голодание благодаря недетерминированному обеспечению блокировок для потоков, входящих в критические разделы).
@codeObserver Вы спросили: «Будет ли вызов notifyAll () привести к тому, что несколько ожидающих потоков одновременно будут проверять условие while ()… и, следовательно, существует вероятность того, что до того, как время будет запущено, 2 потока уже вышли из него, вызывая outOfBound исключение ?." Нет, это невозможно, поскольку, хотя несколько потоков просыпаются, они не могут одновременно проверять условие while. Каждый из них должен повторно получить блокировку (сразу после ожидания), прежде чем они смогут повторно войти в раздел кода и повторно проверить это время. Поэтому по одному.
@xagyg хороший пример. Это не по теме исходного вопроса; просто для обсуждения. Тупик - это проблема дизайна imo (поправьте меня, если я ошибаюсь). Потому что у вас есть одна блокировка, разделяемая как put, так и get. И JVM недостаточно умен, чтобы вызывать put после освобождения блокировки и наоборот. Мертвая блокировка возникает из-за того, что put пробуждает другой put, который возвращается в wait () из-за while (). Будет ли работать два класса (и два замка)? Так что положите {synchonized (get)}, get {(synchonized (put)}. Другими словами, get будет только wake put, а put wake get only.
@ Джей, а что, если потребители не ждут? производителям по-прежнему необходимо защищаться друг от друга, поэтому, если существует много разногласий только со стороны производителей, им нужно будет уведомить друг друга о блокировке производителя. при других обстоятельствах могут быть потребители, ожидающие, поэтому вам также нужно будет уведомить их в целом ..... кажется, что необходимы 3 блокировки, производитель сначала получает блокировку производителя, затем ровно 1 производитель ждет совместного производителя-потребителя lock аналогично, потребитель сначала получает блокировку потребителя, а затем ровно 1 потребитель ожидает совместной блокировки. работает?
@xagyg Спасибо за объяснение, проблема четко объяснена. Я все еще думаю, что вы можете безопасно использовать notify в своем примере, если используете две отдельные блокировки: одну для блокировки в пустой очереди, другую для блокировки в полной очереди. Я что-то пропустил ?
Есть ли способ избавиться от цикла while и, возможно, дважды проверить условие? Я не думаю, что это возможно, и это то, что я ответил в интервью, но я хотел подтвердить.
@ user1071840 Вы правы. Невозможно удалить цикл while.
@kiruwka Предлагаемый вами подход является общим для систем параллелизма, предназначенных для ожидания или передачи сигналов об условиях. Java не предоставляет необходимых механизмов для поддержки этого подхода. Использование двух замков для решения этой проблемы с Java не работает.
@Jay интуитивно кажется, что два замка могут сработать ... потому что нас так учили в университете. Однако здесь использовалась не Java, а параллельные системы, поддерживающие ожидание / сигнализацию по условию. Java этого не поддерживает. Вы не можете ждать выполнения условия в Java, а затем сигнализировать потокам, ожидающим этого условия. Как мы знаем, сигнал Java пробуждает все потоки. Итак, нет, две блокировки не будут работать, и вы столкнетесь с условиями гонки с получением и помещением, пытающимися получить / поместить в одну и ту же очередь / буфер.
@xagyg Меня смущает ваша точка входа C1 и C2 на X и Y. Как два потока могут быть введены в синхронизированный блок / метод?
@xagyg Не тот случай, когда notifyAll () также может привести к тупиковой ситуации. В конечном итоге после notifyAll () будет запущен только один поток. Что если даже после использования notifyAll () все условия, которые вы указали, выполнены, тогда в этом случае также произойдет тупик.
@UnKnown Когда C1 вызывает wait(), он снимает блокировку, таким образом, C2 может войти в синхронизированный блок.
@JManish notifyAll() разбудит все потоки, но только 1 поток может получить блокировку и запустить. Если этот поток покидает синхронизированный блок или снова переходит к wait(), у других потоков может быть шанс получить блокировку и начать работу.
@xagyg Вы пишете, что у нас не может быть потока, ожидающего в методе put, а также потока, ожидающего в методе get. Но это неправда, правда ?. Взгляните на свой пример, где ждут как производитель, так и потребитель.
@black Нет. Я сказал, что это "похоже" на это. Я объясняю на примере, как это возможно.
Таким образом, проблема в том, что пробуждение при вызове продюсера может разбудить другого продюсера, что является бессмысленным пробуждением. (или наоборот, Потребитель может разбудить другого Потребителя). Это также можно исправить, используя две отдельные переменные состояния.
@ Mike76 да, если вызывается notify, в некоторых случаях это не только бессмысленно, но и потенциально блокирует (как описано в примере).
notify() пробуждает первый поток, который вызвал wait() для того же объекта.
notifyAll() пробуждает все потоки, которые вызвали wait() на одном и том же объекте.
Первым будет запущен поток с наивысшим приоритетом.
В случае notify() это не совсем "первая нить".
вы не можете предсказать, какой из них будет выбран ВМ. Только Бог знает.
Нет гарантии, кто будет первым (не честно)
Он разбудит первый поток только в том случае, если ОС это гарантирует, а вполне вероятно, что это не так. Он действительно полагается на ОС (и ее планировщик), чтобы определить, какой поток пробудить.
Взгляните на код, опубликованный @xagyg.
Предположим, два разных потока ждут двух разных условий:
первая ветка ожидает buf.size() != MAX_SIZE, а вторая нить ожидает buf.size() != 0.
Допустим в какой-то момент buf.size()не равно 0. JVM вызывает notify() вместо notifyAll(), и уведомляется первый поток (не второй).
Первый поток просыпается, проверяет buf.size(), который может вернуть MAX_SIZE, и возвращается в режим ожидания. Второй поток не просыпается, продолжает ждать и не вызывает get().
Я очень удивлен, что никто не упомянул о печально известной проблеме "потерянного пробуждения" (погуглите).
По сути:
ТОГДА вы должны использовать notifyAll, если у вас нет доказанных гарантий невозможности потерянного пробуждения.
Типичным примером является параллельная очередь FIFO, в которой: несколько enqueuers (1. и 3. выше) могут переводить вашу очередь из пустой в непустую несколько извлекающих из очереди (2. выше) могут ждать выполнения условия «очередь не пуста» пустой -> непустой должен уведомлять участников, вышедших из очереди
Вы можете легко написать чередование операций, в котором, начиная с пустой очереди, взаимодействуют 2 объекта очереди и 2 средства удаления из очереди, а 1 объект очереди останется в спящем режиме.
Это проблема, возможно, сравнимая с проблемой тупика.
Мои извинения, xagyg подробно объясняет это. Название проблемы - «потерянное пробуждение».
@Abhay Bansal: я думаю, вам не хватает того факта, что condition.wait () снимает блокировку и повторно захватывается потоком, который просыпается.
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 и так далее.
Вот более простое объяснение:
Вы правы в том, что независимо от того, используете ли вы notify () или notifyAll (), немедленным результатом будет то, что ровно один другой поток получит монитор и начнет выполнение. (Предполагая, что некоторые потоки были фактически заблокированы в wait () для этого объекта, другие несвязанные потоки не поглощают все доступные ядра и т. д.) Воздействие наступает позже.
Предположим, поток A, B и C ожидают этого объекта, а поток A получает монитор. Разница заключается в том, что происходит, когда A отпускает монитор. Если вы использовали notify (), то B и C все еще заблокированы в wait (): они не ждут на мониторе, они ждут уведомления. Когда A отпускает монитор, B и C все еще сидят там, ожидая notify ().
Если вы использовали notifyAll (), то оба B и C вышли из состояния ожидания уведомления и ожидают получения монитора. Когда A освобождает монитор, либо B, либо C захватят его (при условии, что другие потоки не конкурируют за этот монитор) и начнут выполнение.
Очень четкое объяснение. Результат такого поведения notify () может привести к «пропущенному сигналу» / «пропущенному уведомлению», что приведет к тупиковой ситуации / отсутствию прогресса состояния приложения. P-производитель, C-потребитель P1, P2 и C2 ждут C1. C1 вызывает notify () и предназначен для производителя, но C2 может быть разбужен, поэтому и P1, и P2 пропустили уведомление и будут ждать дальнейшего явного «уведомления» (вызова notify ()).
Я хотел бы упомянуть то, что объясняется в Java Concurrency in Practice:
Во-первых, Notify или NotifyAll?
It will be NotifyAll, and reason is that it will save from signall hijacking.
If two threads A and B are waiting on different condition predicates of same condition queue and notify is called, then it is upto JVM to which thread JVM will notify.
Now if notify was meant for thread A and JVM notified thread B, then thread B will wake up and see that this notification is not useful so it will wait again. And Thread A will never come to know about this missed signal and someone hijacked it's notification.
So, calling notifyAll will resolve this issue, but again it will have performance impact as it will notify all threads and all threads will compete for same lock and it will involve context switch and hence load on CPU. But we should care about performance only if it is behaving correctly, if it's behavior itself is not correct then performance is of no use.
Эта проблема может быть решена с использованием объекта Condition явной блокировки Lock, представленного в jdk 5, поскольку он обеспечивает разное ожидание для каждого предиката условия. Здесь он будет вести себя правильно, и проблем с производительностью не возникнет, так как он вызовет сигнал и убедится, что только один поток ожидает этого условия.
notify будет уведомлять только один поток, который находится в состоянии ожидания, в то время как notify all будет уведомлять все потоки в состоянии ожидания, теперь все уведомленные потоки и все заблокированные потоки имеют право на блокировку, из которых только один получит блокировку и все остальные (включая тех, кто ранее находился в состоянии ожидания) будут в заблокированном состоянии.
Подводя итог превосходным подробным объяснениям, приведенным выше, и самым простым способом, который я могу придумать, это связано с ограничениями встроенного монитора JVM, который 1) приобретается на всем блоке синхронизации (блоке или объекте) и 2) не различает конкретное условие, которое ожидается / уведомлено / о котором.
Это означает, что если несколько потоков ожидают в разных условиях и используется notify (), выбранный поток может не быть тем, который будет прогрессировать по вновь выполненному условию, что приведет к тому, что этот поток (и другие все еще ожидающие потоки, которые смогут выполнить условие и т. д.), чтобы не добиться прогресса и, в конечном итоге, голодания или зависания программы.
Напротив, notifyAll () позволяет всем ожидающим потокам в конечном итоге повторно получить блокировку и проверить их соответствующее состояние, тем самым в конечном итоге позволяя достичь прогресса.
Таким образом, notify () можно безопасно использовать только в том случае, если какой-либо ожидающий поток гарантированно разрешит прогресс, если он будет выбран, что в целом выполняется, когда все потоки в одном мониторе проверяют только одно и то же условие - довольно редкое случай в реальных приложениях.
Краткое содержание:
Всегда отдавайте предпочтение notifyAll () перед уведомлять(), если только у вас нет приложения с массовым параллелизмом, в котором большое количество потоков выполняют одно и то же.
Объяснение:
notify() [...] wakes up a single thread. Because notify() doesn't allow you to specify the thread that is woken up, it is useful only in massively parallel applications — that is, programs with a large number of threads, all doing similar chores. In such an application, you don't care which thread gets woken up.
источник: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Сравните уведомлять() с notifyAll () в описанной выше ситуации: приложение с массовым параллелизмом, в котором потоки делают то же самое. Если вы вызовете notifyAll () в этом случае, notifyAll () вызовет пробуждение (то есть планирование) огромного количества потоков, многие из них без необходимости (поскольку только один поток может фактически продолжить, а именно поток, которому будет предоставлен монитор для объекта ждать(), уведомлять() или notifyAll () был вызван), поэтому расходуются вычислительные ресурсы.
Таким образом, если у вас нет приложения, в котором огромное количество потоков одновременно выполняет одно и то же, предпочтите notifyAll (), а не уведомлять(). Почему? Потому что, как уже ответили другие пользователи в этом форуме, уведомлять()
wakes up a single thread that is waiting on this object's monitor. [...] The choice is arbitrary and occurs at the discretion of the implementation.
источник: Java SE8 API (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)
Представьте, что у вас есть потребительское приложение производителя, в котором потребители готовы (то есть ждать() ing) к потреблению, производители готовы (то есть ждать() ing) к производству, а очередь элементов (которые должны быть произведены / потреблены) пуста. В этом случае уведомлять() может пробудить только потребителей и никогда не пробудить производителей, потому что выбор, кто пробуждается, - это произвольный. Потребительский цикл производителя не продвинется вперед, хотя производители и потребители готовы соответственно производить и потреблять. Вместо этого потребитель просыпается (т.е. выходит из состояния ждать()), не берет элемент из очереди, потому что он пуст, и уведомлять() - другой потребитель для продолжения.
Напротив, notifyAll () пробуждает как производителей, так и потребителей. Выбор расписания зависит от планировщика. Конечно, в зависимости от реализации планировщика, планировщик также может планировать только потребителей (например, если вы назначаете потокам потребителей очень высокий приоритет). Однако здесь предполагается, что опасность того, что планировщик планирует только потребителей, ниже, чем опасность того, что JVM разбудит только потребителей, потому что любой разумно реализованный планировщик не принимает только решения произвольный. Скорее, большинство реализаций планировщика прилагают хотя бы некоторые усилия, чтобы предотвратить голодание.
notify() - выбирает случайный поток из набора ожидания объекта и переводит его в состояние BLOCKED. Остальные потоки в наборе ожидания объекта все еще находятся в состоянии WAITING.
notifyAll() - переводит все потоки из набора ожидания объекта в состояние BLOCKED. После использования notifyAll() в наборе ожидания общего объекта не остается ни одного потока, поскольку все они теперь находятся в состоянии BLOCKED, а не в состоянии WAITING.
BLOCKED - заблокирован для получения блокировки.
WAITING - ожидает уведомления (или заблокирован для завершения присоединения).
Когда вы вызываете wait () для «объекта» (ожидая, что блокировка объекта будет получена), intern это освободит блокировку этого объекта и поможет другим потокам заблокировать этот «объект», в этом сценарии будет более 1 потока, ожидающего «ресурса / объекта» (учитывая, что другие потоки также вызвали ожидание для того же указанного выше объекта, и в дальнейшем будет поток, который заполняет ресурс / объект и вызывает notify / notifyAll).
Здесь, когда вы отправляете уведомление об одном и том же объекте (с той же / другой стороны процесса / кода), это освобождает заблокированный и ожидающий одиночный поток (не все ожидающие потоки - этот выпущенный поток будет выбран потоком JVM Планировщик и весь процесс получения блокировки на объекте такой же, как и в обычном режиме).
Если у вас есть только один поток, который будет совместно использовать / работать с этим объектом, можно использовать только метод notify () в вашей реализации ожидания-уведомления.
теперь я смотрю, как именно jvm идентифицирует и прерывает ожидающий поток, когда мы запускаем notify () для объекта ...
У потока есть три состояния.
Теперь, когда вызывается notify (), JVM выбирает один поток и переводит его в состояние ЗАБЛОКИРОВАНО и, следовательно, в состояние RUNNING, поскольку за объект монитора нет конкуренции.
Когда вызывается notifyAll (), JVM выбирает все потоки и переводит их в состояние ЗАБЛОКИРОВАНО. Все эти потоки получат блокировку объекта в приоритетном порядке. Поток, который первым сможет получить монитор, сможет сначала перейти в состояние РАБОТАЕТ и так далее.
Просто потрясающее объяснение.
Хотя выше есть несколько убедительных ответов, я удивлен количеством недоразумений и недоразумений, которые я прочитал. Это, вероятно, подтверждает идею о том, что следует как можно чаще использовать java.util.concurrent вместо того, чтобы пытаться написать свой собственный неработающий параллельный код.
Вернемся к вопросу: резюмируя, лучшая практика сегодня - ИЗБЕГАТЬ notify () во ВСЕХ ситуациях из-за проблемы с потерей пробуждения. Любой, кто этого не понимает, не должен иметь права писать критически важный код параллелизма. Если вас беспокоит проблема стада, один из безопасных способов пробудить по одной нити за раз - это:
Или вы можете использовать Java.util.concurrent. *, В котором это уже реализовано.
По моему опыту, использование ожидания / уведомления часто используется в механизмах очередей, где поток (реализация Runnable) обрабатывает содержимое очереди. Затем wait() используется всякий раз, когда очередь пуста. И notify() вызывается при добавлении информации. -> в таком случае есть только 1 поток, который когда-либо вызывает wait(), тогда не выглядит ли глупо использовать notifyAll(), если вы знаете, что есть только 1 ожидающий поток.
Этот ответ представляет собой графическое переписывание и упрощение отличного ответа xagyg, включая комментарии эран.
Зачем использовать notifyAll, даже если каждый продукт предназначен для одного потребителя?
Рассмотрим производителей и потребителей в упрощенном виде.
Режиссер:
while (!empty) {
wait() // on full
}
put()
notify()
Потребитель:
while (empty) {
wait() // on empty
}
take()
notify()
Предположим, 2 производителя и 2 потребителя совместно используют буфер размером 1. На следующем рисунке показан сценарий, приводящий к тупик, которого можно было бы избежать, если бы все потоки использовали notifyAll.
Каждое уведомление помечено активизируемым потоком.

Взято из блог на Эффективной 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 документы):
Вы можете предположить, что с каждым объектом блокировки связаны очереди двух типов. Одна - это заблокированная очередь, содержащая поток, ожидающий блокировки монитора, другая - это очередь ожидания, содержащая поток, ожидающий уведомления. (Поток будет помещен в очередь ожидания, когда они вызовут Object.wait).
Каждый раз, когда блокировка становится доступной, планировщик выбирает для выполнения один поток из заблокированной очереди.
Когда вызывается notify, только один поток в очереди ожидания помещается в заблокированную очередь для борьбы за блокировку, в то время как notifyAll помещает весь поток в очереди ожидания в заблокированную очередь.
Теперь вы видите разницу?
Хотя в обоих случаях будет выполняться только один поток, но с notifyAll, другие потоки все равно получают изменение, которое должно быть выполнено (потому что они находятся в заблокированной очереди), даже если они не смогли бороться с блокировкой.
Я в основном рекомендую использовать notifyAll постоянно, хотя это может немного снизить производительность.
И используйте notify, только если:
Например:
Ответ @xagyg дает пример того, что notify вызовет тупик. В его примере и производитель, и потребитель связаны с одним и тем же объектом блокировки. Поэтому, когда производитель вызывает notify, можно уведомить либо производителя, либо потребителя. Но если производитель проснулся, он не может продолжить выполнение программы, потому что буфер уже заполнен, и возникает тупик.
Решить ее можно двумя способами:
notifyALl, как предлагает @xagyg.
Полезные библиотеки, которые можно использовать для параллелизма, находятся в библиотеках параллелизма. Я считаю, что это лучший выбор почти в каждом случае. Библиотека Concurency до версии Java 5.0 (в которой они были добавлены в качестве стандарта в 2004 году)