Если кто-то Googles для «разницы между notify()
и notifyAll()
», то появится много объяснений (за исключением абзацев Javadoc). Все сводится к числу ожидающих потоков: один вход notify()
и все вход notifyAll()
.
Однако (если я правильно понимаю разницу между этими методами), всегда выбирается только один поток для дальнейшего получения монитора; в первом случае тот, который выбран виртуальной машиной, во втором случае тот, который выбран планировщиком системного потока. Точные процедуры выбора для них обоих (в общем случае) не известны программисту.
Что полезно разница между уведомит () и notifyAll () тогда? Я что-то пропустил?
java
multithreading
Сергей Миханов
источник
источник
synchronized
, за исключением некоторых случаев. , довольно редкие случаи использования.notifyAll()
случае отсутствует то, что в этом случае _ другие потоки после первого остаются активными и получают монитор один за другим. В этомnotify
случае ни один из других потоков даже не проснулся. Так что функционально они очень разные!blocked
не так.waiting
Когдаblocked
его exec временно приостановлен, пока другой поток не окажется внутриsync
блока.Ответы:
Это не правильно.
o.notifyAll()
будит все потоки, которые заблокированы вo.wait()
вызовах. Нити разрешается только вернуться изo.wait()
одного за другим, но каждый из них будет получать свою очередь.Проще говоря, это зависит от того, почему ваши темы ожидают уведомления. Вы хотите сообщить одному из ожидающих потоков, что что-то произошло, или вы хотите рассказать обо всех в одно и то же время?
В некоторых случаях все ожидающие потоки могут предпринять полезные действия после завершения ожидания. Примером может служить набор потоков, ожидающих завершения определенной задачи; По завершении задачи все ожидающие потоки могут продолжить свою работу. В таком случае вы должны использовать notifyAll () для пробуждения всех ожидающих потоков одновременно.
В другом случае, например, взаимоисключающей блокировки, только один из ожидающих потоков может сделать что-то полезное после получения уведомления (в этом случае получить блокировку). В таком случае вы бы предпочли использовать notify () . Правильно реализованный, вы также можете использовать notifyAll () в этой ситуации, но вы бы без необходимости разбудили потоки, которые все равно ничего не могут сделать.
Во многих случаях код для ожидания условия будет записан в виде цикла:
Таким образом, если
o.notifyAll()
вызов пробуждает более одного ожидающего потока, а первый, вернувшийся изo.wait()
make, оставляет условие в ложном состоянии, то остальные разбудившие потоки вернутся к ожиданию.источник
Ясно, что
notify
пробуждает (любой) один поток в наборе ожидания,notifyAll
пробуждает все потоки в наборе ожидания. Следующее обсуждение должно прояснить любые сомнения.notifyAll
следует использовать большую часть времени. Если вы не уверены, какой из них использовать, используйте.notifyAll
Пожалуйста, ознакомьтесь с нижеследующим объяснением.Читай очень внимательно и понимай. Пожалуйста, отправьте мне письмо, если у вас есть какие-либо вопросы.
Посмотрите на производителя / потребителя (предположим, это класс ProducerConsumer с двумя методами). Он сломан (потому что он использует
notify
) - да, он МОЖЕТ работать - даже большую часть времени, но это может также вызвать тупик - мы увидим, почему:ВО-ПЕРВЫХ,
Зачем нам нужен цикл while, окружающий ожидание?
Нам нужен
while
цикл на случай, если мы получим эту ситуацию:Потребитель 1 (C1) входит в синхронизированный блок, а буфер пуст, поэтому C1 помещается в набор ожидания (через
wait
вызов). Потребитель 2 (C2) собирается войти в синхронизированный метод (в точке Y выше), но Producer P1 помещает объект в буфер и затем вызываетnotify
. Единственный ожидающий поток - это C1, поэтому он проснулся и теперь пытается повторно получить блокировку объекта в точке X (выше).Теперь C1 и C2 пытаются получить блокировку синхронизации. Один из них (недетерминированный) выбирается и входит в метод, другой блокируется (не ожидает - но блокируется, пытаясь получить блокировку для метода). Допустим, C2 сначала получает блокировку. C1 все еще блокирует (пытается получить блокировку в X). C2 завершает метод и снимает блокировку. Теперь С1 получает блокировку. Угадайте, что, к счастью, у нас есть
while
цикл, потому что C1 выполняет проверку цикла (защита) и не может удалить несуществующий элемент из буфера (C2 уже получил его!). Если бы у нас не было awhile
, мы получили бы,IndexArrayOutOfBoundsException
поскольку C1 пытается удалить первый элемент из буфера!СЕЙЧАС ЖЕ,
Хорошо, теперь почему мы должны уведомить все?
В приведенном выше примере производителя / потребителя это выглядит так, как будто мы можем сойти с рук
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 char - блок при входе в
get
методШАГ 5:
- C1 выполняет
get
метод - получает символ, вызываетnotify
, выходит из метода-
notify
Пробуждается P2- НО, C2 входит в метод до того, как P2 может (P2 должен повторно захватить блокировку), поэтому P2 блокируется при входе в
put
метод- C2 проверяет цикл ожидания, больше нет символов в буфере, поэтому ждет
- C3 входит в метод после C2, но перед P2 проверяет цикл ожидания, больше нет символов в буфере, поэтому ждет
ШАГ 6:
- СЕЙЧАС: P3, C2 и C3 ждут!
- наконец P2 получает блокировку, помещает символ в буфер, вызывает notify, выходит из метода
ШАГ 7:
- Уведомление P2 пробуждает P3 (помните, что любой поток может быть разбужен)
- P3 проверяет состояние цикла ожидания, в буфере уже есть символ, поэтому ждет.
- НЕТ БОЛЬШЕ НИТЕЙ, КОТОРЫЕ ВЫЗВАТЬ, УВЕДОМЛЯЮТ, И ТРИ НИТИ ПОСТОЯННО ПРИВЕДЕНЫ!
РЕШЕНИЕ: Заменить
notify
сnotifyAll
в коде производителя / потребителя (выше).источник
notify
P3 (выбранный поток в этом примере) следует из точки, которую он ожидал (то есть внутриwhile
цикла). Существуют и другие примеры, которые не вызывают взаимоблокировки, однако в этом случае использованиеnotify
не гарантирует код без взаимоблокировок. ИспользованиеnotifyAll
делает.Полезные отличия:
Используйте notify (), если все ваши ожидающие потоки являются взаимозаменяемыми (порядок их активации не имеет значения), или если у вас есть только один ожидающий поток. Типичным примером является пул потоков, используемый для выполнения заданий из очереди - при добавлении задания один из потоков получает уведомление о пробуждении, выполнении следующего задания и возвращении в спящий режим.
Используйте notifyAll () для других случаев, когда ожидающие потоки могут иметь разные цели и должны иметь возможность работать одновременно. Примером является операция обслуживания общего ресурса, когда несколько потоков ожидают завершения операции, прежде чем получить доступ к ресурсу.
источник
Я думаю, что это зависит от того, как ресурсы производятся и потребляются. Если одновременно доступны 5 рабочих объектов и у вас есть 5 потребительских объектов, имеет смысл разбудить все потоки с помощью notifyAll (), чтобы каждый из них мог обработать 1 рабочий объект.
Если у вас есть только один доступный рабочий объект, какой смысл в том, чтобы разбудить все потребительские объекты в гонке за этот один объект? Первая, проверяющая доступную работу, получит ее, а все остальные потоки проверит и обнаружат, что им нечего делать.
Я нашел отличное объяснение здесь . Короче говоря:
источник
Обратите внимание, что с утилитами параллелизма у вас также есть выбор между
signal()
иsignalAll()
как эти методы вызываются там. Так что вопрос остается в силе даже сjava.util.concurrent
.Даг Ли поднимает интересный момент в своей знаменитой книге : если
notify()
иThread.interrupt()
произойдет одновременно, уведомление может фактически потеряться. Если это может произойти и имеет драматические последствия, тоnotifyAll()
это более безопасный выбор, даже если вы платите слишком много времени (большую часть времени вы пробуждаете слишком много потоков).источник
Краткое содержание:
Всегда предпочитают notifyAll () над уведомит () , если у вас нет массивно параллельных приложений , где большое число потоков все делать то же самое.
Объяснение:
источник: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Сравните notify () с notifyAll () в описанной выше ситуации: массивно параллельное приложение, где потоки делают то же самое. Если в этом случае вы вызываете notifyAll () , notifyAll () будет вызывать пробуждение (то есть планирование) огромного количества потоков, многие из которых неоправданно (поскольку фактически может продолжаться только один поток, а именно поток, которому будет предоставлен следить за объектом ( были вызваны wait () , notify () или notifyAll () ) , что приводит к потере вычислительных ресурсов.
Таким образом, если у вас нет приложения, в котором огромное количество потоков выполняет одно и то же одновременно, предпочтение notifyAll () перед notify () . Почему? Потому что, как другие пользователи уже ответили на этом форуме, уведомить ()
источник: Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )
Представьте, что у вас есть приложение потребителя производителя, в котором потребители готовы (то есть, ожидают () ) потреблять, производители готовы (то есть, ждать () ), чтобы произвести, и очередь товаров (которые будут произведены / потреблены) пуста. В этом случае notify () может разбудить только потребителей, а не производителей, потому что выбор, который пробуждается, является произвольным . Потребительский цикл производителей не будет прогрессировать, хотя производители и потребители готовы производить и потреблять соответственно. Вместо этого потребитель просыпается (то есть покидает состояние wait () ), не вынимает элемент из очереди, поскольку он пуст, и уведомляет другого потребителя (), чтобы продолжить.
Напротив, notifyAll () пробуждает как производителей, так и потребителей. Выбор того, кто запланирован, зависит от планировщика. Конечно, в зависимости от реализации планировщика, планировщик может также только планировать потребителей (например, если вы назначаете потокам потребителей очень высокий приоритет). Однако в данном случае предполагается, что опасность планирования планировщика только для потребителей ниже, чем опасность того, что JVM только разбудит потребителей, потому что любой разумно реализованный планировщик не принимает только произвольные решения. Скорее, большинство реализаций планировщика предпринимают по крайней мере некоторое усилие, чтобы предотвратить голодание.
источник
Вот пример. Запустить его. Затем измените один из notifyAll () на notify () и посмотрите, что произойдет.
Класс ProducerConsumerExample
Класс Dropbox
Потребительский класс
Продюсерский класс
источник
От Джошуа Блоха, самого Гуру Java в Effective Java 2nd edition:
«Пункт 69: Предпочитают утилиты параллелизма ждать и уведомлять».
источник
Я надеюсь, что это прояснит некоторые сомнения.
notify () : метод notify () запускает один поток, ожидающий блокировки (первый поток, вызвавший wait () для этой блокировки).
notifyAll () : метод notifyAll () пробуждает все потоки, ожидающие блокировки; JVM выбирает один из потоков из списка потоков, ожидающих блокировки, и пробуждает этот поток.
В случае единственного потока, ожидающего блокировки, нет существенной разницы между notify () и notifyAll (). Однако, когда существует более одного потока, ожидающего блокировки, и в notify (), и в notifyAll () точный пробужденный поток находится под контролем JVM, и вы не можете программно контролировать пробуждение определенного потока.
На первый взгляд кажется, что хорошей идеей является просто вызвать notify () для пробуждения одного потока; может показаться ненужным разбудить все темы. Однако проблема с notify () состоит в том, что пробужденный поток может не подходить для пробуждения (поток может ожидать какого-то другого условия, или условие все еще не удовлетворено для этого потока и т. Д.). В этом случае функция notify () может быть потеряна, и никакой другой поток не проснется, что потенциально приведет к типу взаимоблокировки (уведомление потеряно, и все другие потоки ожидают уведомления - навсегда).
Чтобы избежать этой проблемы , всегда лучше вызывать notifyAll (), когда более одного потока ожидает блокировки (или более одного условия, при котором выполняется ожидание). Метод notifyAll () пробуждает все потоки, поэтому он не очень эффективен. однако эта потеря производительности незначительна в реальных приложениях.
источник
Есть три состояния для потока.
Теперь, когда вызывается notify (), JVM выбирает один поток и переводит его в состояние BLOCKED и, следовательно, в состояние RUNNING, поскольку нет конкуренции за объект монитора.
Когда вызывается notifyAll (), JVM выбирает все потоки и переводит их в состояние BLOCKED. Все эти потоки получат блокировку объекта в приоритетном порядке. Поток, который может получить монитор первым, сможет сначала перейти в состояние RUNNING и так далее.
источник
Я очень удивлен, что никто не упомянул печально известную проблему «потерянного пробуждения» (Google google).
В принципе:
ТОГДА вы должны использовать notifyAll, если у вас нет доказуемой гарантии невозможности потерянных пробуждений.
Типичным примером является параллельная очередь FIFO, в которой: несколько обработчиков (1. и 3. выше) могут перевести вашу очередь из пустых в непустые, несколько разделителей (2. выше) могут ожидать условия «очередь не пуста» пусто -> непустой должен уведомить декуайеров
Вы можете легко написать чередование операций, в которых, начиная с пустой очереди, взаимодействуют 2 энкейера и 2 декуейера, а один энкейер останется спящим.
Эта проблема, возможно, сопоставима с проблемой тупика.
источник
notify()
проснется одна нить, покаnotifyAll()
все проснется. Насколько я знаю, нет никакого среднего уровня. Но если вы не уверены, чтоnotify()
будет делать с вашими темами, используйтеnotifyAll()
. Работает как шарм каждый раз.источник
Все вышеприведенные ответы верны, насколько я могу судить, поэтому я собираюсь рассказать вам кое-что еще. Для производственного кода вы действительно должны использовать классы в java.util.concurrent. Они очень мало могут сделать для вас, в области параллелизма в Java.
источник
notify()
позволяет писать более эффективный код, чемnotifyAll()
.Рассмотрим следующий фрагмент кода, который выполняется из нескольких параллельных потоков:
Это можно сделать более эффективным, используя
notify()
:В случае, если у вас есть большое количество потоков, или если условие цикла ожидания является дорогостоящим для оценки,
notify()
будет значительно быстрее, чемnotifyAll()
. Например, если у вас 1000 потоков, 999 потоков будут пробуждены и оценены после первогоnotifyAll()
, затем 998, затем 997 и так далее. Напротив, сnotify()
решением будет пробуждена только одна нить.Используйте,
notifyAll()
когда вам нужно выбрать, какой поток будет работать дальше:Наконец, важно понимать, что в случае
notifyAll()
, если код внутриsynchronized
блоков, которые были разбужены, будет выполняться последовательно, а не все сразу. Допустим, в приведенном выше примере ожидают три потока, а четвертый поток вызываетnotifyAll()
. Будут пробуждены все три потока, но только один начнет выполнение и проверит состояниеwhile
цикла. Если условие выполненоtrue
, он будет вызыватьсяwait()
снова, и только тогда второй поток начнет выполняться и проверит состояние своегоwhile
цикла, и так далее.источник
Вот более простое объяснение:
Вы правы, что независимо от того, используете ли вы notify () или notifyAll (), немедленный результат состоит в том, что ровно один другой поток получит монитор и начнет выполнение. (Предполагая, что некоторые потоки были фактически заблокированы в wait () для этого объекта, другие не связанные потоки не впитывают все доступные ядра и т. Д.) Влияние наступит позже.
Предположим, что поток A, B и C ожидали этого объекта, а поток A получает монитор. Разница заключается в том, что происходит, когда А выпускает монитор. Если вы использовали notify (), то B и C по-прежнему блокируются в wait (): они не ждут на мониторе, они ожидают уведомления. Когда A отпускает монитор, B и C все еще сидят там, ожидая уведомления ().
Если вы использовали notifyAll (), то B и C оба вышли за состояние «ожидания уведомления» и оба ожидают получения монитора. Когда A освобождает монитор, B или C получат его (при условии, что другие потоки не конкурируют за этот монитор) и начнут выполнение.
источник
Этот ответ представляет собой графическое переписывание и упрощение превосходного ответа от xagyg , включая комментарии от eran .
Зачем использовать notifyAll, даже если каждый продукт предназначен для одного потребителя?
Рассмотрим производителей и потребителей, упрощенно следующим образом.
Режиссер:
Потребитель:
Предположим, 2 производителя и 2 потребителя совместно используют буфер размером 1. На следующем рисунке показан сценарий, приводящий к тупику , которого можно было бы избежать, если бы все потоки использовали notifyAll .
Каждое уведомление помечается пробуждаемой нитью.
источник
Я хотел бы упомянуть, что объясняется в Java Concurrency на практике:
Первый пункт, будь то Notify или NotifyAll?
Эта проблема может быть решена с помощью объекта Condition явной блокировки Lock, представленного в jdk 5, поскольку он обеспечивает различное ожидание для каждого предиката условия. Здесь он будет вести себя правильно и не будет проблем с производительностью, поскольку вызовет сигнал и убедится, что только один поток ожидает этого условия.
источник
notify()
- Выбирает случайный поток из набора ожидания объекта и переводит его вBLOCKED
состояние. Остальные потоки в наборе ожидания объекта все еще находятся вWAITING
состоянии.notifyAll()
- Перемещает все потоки из набора ожидания объекта вBLOCKED
состояние. После использованияnotifyAll()
в наборе ожидания общего объекта не осталось потоков, поскольку все они теперь находятся вBLOCKED
состоянии, а не вWAITING
состоянии.BLOCKED
- заблокирован для получения блокировки.WAITING
- ожидание уведомления (или блокировка для завершения соединения).источник
Взято из блога об эффективной Java:
Итак, что я понимаю (из вышеупомянутого блога, комментарий "Yann TM" к принятому ответу и документации по Java ):
источник
Посмотрите на код, размещенный @xagyg.
Предположу , что два разных потоков ждут два различных условий: первая нить ждет , а второй поток ждет .
buf.size() != MAX_SIZE
buf.size() != 0
Предположим, что в какой-то момент
buf.size()
не равно 0 . JVM вызываетnotify()
вместоnotifyAll()
, и первый поток уведомляется (не второй).Первый поток просыпается, проверяет,
buf.size()
может ли он вернутьсяMAX_SIZE
, и возвращается к ожиданию. Второй поток не проснулся, продолжает ждать и не звонитget()
.источник
notify()
пробуждает первый поток, который вызвалwait()
на тот же объект.notifyAll()
пробуждает все потоки, вызвавшие один иwait()
тот же объект.Поток с наивысшим приоритетом будет запущен первым.
источник
notify()
это не совсем « первый поток ».notify уведомит только один поток, находящийся в состоянии ожидания, в то время как notify all уведомит все потоки в состоянии ожидания, теперь все уведомленные потоки и все заблокированные потоки имеют право на блокировку, из которых только один получит блокировку и все остальные (включая тех, кто ранее находился в состоянии ожидания) будут в заблокированном состоянии.
источник
Подводя итог прекрасным подробным объяснениям, приведенным выше, и самым простым способом, который я могу себе представить, это связано с ограничениями встроенного монитора JVM, который 1) приобретается для всего блока синхронизации (блока или объекта) и 2) не делает различий в отношении конкретного условия, ожидающего / уведомленного о / о.
Это означает, что если несколько потоков ожидают выполнения в разных условиях и используется notify (), то выбранный поток может быть не тем, который будет прогрессировать в только что выполненном состоянии, что приведет к тому, что этот поток (и другие в настоящий момент все еще ожидающие потоки будут способны выполнить условие и т. д.) не иметь возможности добиться прогресса, и в конечном итоге голодать или зависать программы.
Напротив, notifyAll () позволяет всем ожидающим потокам в конечном итоге повторно захватить блокировку и проверить их соответствующее состояние, тем самым, в конечном итоге, позволяя добиться прогресса.
Таким образом, notify () может использоваться безопасно только в том случае, если любой ожидающий поток гарантированно разрешит выполнение прогресса в случае его выбора, что в общем случае удовлетворяется, когда все потоки в одном мониторе проверяют только одно и то же условие - довольно редко случай в реальных приложениях.
источник
Когда вы вызываете wait () для «объекта» (ожидая, что блокировка объекта получена), интерн это снимет блокировку с этого объекта и поможет другим потокам заблокировать этот «объект», в этом сценарии будет более чем один поток ожидает «ресурс / объект» (учитывая, что другие потоки также выдавали ожидание для того же вышеупомянутого объекта, и вниз по пути будет поток, который заполняет ресурс / объект и вызывает notify / notifyAll).
Здесь, когда вы отправляете уведомление об одном и том же объекте (с той же / другой стороны процесса / кода), это освобождает заблокированный и ожидающий один поток (не все ожидающие потоки - этот освобожденный поток будет выбран потоком JVM). Планировщик и весь процесс получения блокировки на объекте такой же, как и обычный).
Если у вас есть только один поток, который будет совместно использовать этот объект, то в вашей реализации wait-notify можно использовать только метод notify ().
если вы находитесь в ситуации, когда более одного потока читает и записывает ресурсы / объекты на основе вашей бизнес-логики, тогда вам следует обратиться к notifyAll ()
теперь я смотрю, как именно jvm идентифицирует и прерывает ожидающий поток, когда мы запускаем notify () для объекта ...
источник
Хотя выше есть несколько убедительных ответов, я удивлен тем количеством недоразумений и недоразумений, которые я прочитал. Это, вероятно, подтверждает идею о том, что следует как можно больше использовать java.util.concurrent вместо того, чтобы пытаться писать собственный неработающий параллельный код. Вернемся к вопросу: подведем итог: сегодня лучше избегать уведомления () во всех ситуациях из-за проблемы с пробуждением. Любой, кто не понимает этого, не должен иметь права писать критически важный код параллелизма. Если вы беспокоитесь о проблеме скотоводства, то один из безопасных способов добиться пробуждения одного потока за раз: 1. Создать явную очередь ожидания для ожидающих потоков; 2. Пусть каждый поток в очереди ожидает своего предшественника; 3. Пусть каждый поток вызывает notifyAll () после завершения. Или вы можете использовать Java.util.concurrent. *,
источник
Runnable
реализация) обрабатывает содержимое очереди.wait()
Затем используется всякий раз , когда очередь пуста. Иnotify()
вызывается при добавлении информации. -> в таком случае есть только 1 поток, который когда-либо вызываетwait()
, тогда не выглядит немного глупо использовать a,notifyAll()
если вы знаете, что есть только 1 ожидающий поток.Просыпаться все не имеет большого значения здесь. ждать уведомления и уведомления, все это ставится после владения монитором объекта. Если поток находится в стадии ожидания и вызывается notify, этот поток займет блокировку, и никакой другой поток в этой точке не сможет принять эту блокировку. Таким образом, одновременный доступ не может иметь место вообще. Насколько мне известно, любой вызов wait и notifyall можно сделать только после взятия блокировки на объекте. Поправь меня, если я ошибаюсь.
источник