Я попытаюсь объяснить вам статическую вещь. Прежде всего статические переменные не принадлежат ни одному конкретному экземпляру класса. Они распознаются с именем класса. Статические методы снова не относятся к какому-либо конкретному экземпляру. Они могут получить доступ только к статическим переменным. Представьте, что вы вызываете MyClass.myMethod (), а myMethod - это статический метод. Если вы используете нестатические переменные внутри метода, как, черт возьми, он знает, какие переменные использовать? Вот почему вы можете использовать из статических методов только статические переменные. Повторяю, они НЕ принадлежат ни одному конкретному экземпляру.
Поскольку wait и notify используются для реализации [condition variables] ( http://en.wikipedia.org/wiki/Monitor_ (синхронизация) #Blocking_condition_variables) , и поэтому вам нужно проверить, определенный предикат, который вы ожидаете, верен, прежде чем продолжить.
Из вашего вопроса:
Я прочитал, что мы должны всегда вызывать wait () из цикла:
blockquote>Хотя wait () обычно до тех пор, пока не будет вызвана notify () или notifyAll (), существует вероятность, что в очень редких случаях ожидание потока может быть пробуждено из-за ложного пробуждения. В этом случае поток ожидания возобновляется без уведомления () или notifyAll (), который был вызван.
По сути, поток возобновляется без видимых причин.
Из-за этой удаленной возможности , Oracle рекомендует, чтобы вызовы wait () должны выполняться в цикле, который проверяет условие ожидания потока.
wait
следует вызывать в цикле. Это связано с тем, что какой-то другой поток мог обработать условие.
– David Schwartz
8 February 2016 в 19:19
Вам нужно не только закорачивать, но и проверять свое состояние в цикле. Java не гарантирует, что ваш поток будет разбужен только вызовом notify () / notifyAll () или правильным уведомлением () / notifyAll () вообще. Из-за этого свойства версия без цикла может работать в вашей среде разработки и неожиданно завершается в рабочей среде.
Например, вы чего-то ждете:
synchronized (theObjectYouAreWaitingOn) {
while (!carryOn) {
theObjectYouAreWaitingOn.wait();
}
}
злая нить приходит и:
theObjectYouAreWaitingOn.notifyAll();
Если злая нить не может / не может возиться с carryOn
, вы просто продолжаете ждать подходящего клиента.
Изменить: Добавлено еще несколько образцов. Ожидание может быть прервано. Он выбрасывает InterruptedException, и вам может потребоваться завернуть wait в try-catch. В зависимости от потребностей вашего бизнеса вы можете выйти или подавить исключение и продолжить ожидание.
Почему wait () всегда следует вызывать внутри цикла
blockquote>. Основная причина, по которой петли
while
так важны, - это условия гонки между потоками. Разумеется, ложные пробуждения реальны, а для некоторых архитектур они являются общими, но условия гонки являются гораздо более вероятной причиной для циклаwhile
.Например:
synchronized (queue) { // this needs to be while while (queue.isEmpty()) { queue.wait(); } queue.remove(); }
С приведенный выше код, может быть 2 потребительских потока. Когда производитель блокирует
queue
, чтобы добавить к нему, потребитель № 1 может быть заблокирован в блокировкеsynchronized
, пока потребитель # 2 ожидает наqueue
. Когда элемент добавляется в очередь иnotify
вызывается, # 2 перемещается из очереди ожидания, которая должна блокироваться блокировкойqueue
, но будет позади потребителя # 1, который был уже заблокирован на замке. Это означает, что потребитель № 1 сначала отправляется вперед, чтобы вызватьremove
изqueue
. Если циклwhile
- это толькоif
, тогда, когда потребитель # 2 получает блокировку и вызываетremove
, возникает исключение, потому чтоqueue
теперь пуст - другой потребительский поток уже удалил элемент из очередь. Просто потому, что он был уведомлен, он должен убедиться, чтоqueue
по-прежнему пуст из-за этого состояния гонки.Это хорошо документировано. Вот веб-страница, которую я создал некоторое время назад, которая подробно объясняет условие гонки и имеет некоторый пример кода.
Может быть, только один работник ждет, пока условие станет истинным.
Если два или более рабочих бодрствуют (notifyAll), они должны снова проверить это условие. в противном случае все работники продолжали бы работать, хотя могут быть только данные для одного из них.
При использовании механизма wait / notify важны безопасность и жизнеспособность. Свойство безопасности требует, чтобы все объекты поддерживали согласованные состояния в многопоточной среде. Свойство liveness требует, чтобы каждая операция или вызов метода выполнялись до завершения без прерывания.
Чтобы гарантировать жизнеспособность, программы должны проверить условие цикла while перед вызовом метода wait (). Этот ранний тест проверяет, удовлетворил ли другой поток предикату условия и отправил уведомление. Вызов метода wait () после отправки уведомления приводит к неопределенной блокировке.
Чтобы гарантировать безопасность, программы должны проверить условие цикла while после возврата из метода wait (). Хотя wait () предназначен для блокировки неограниченно до получения уведомления, он все равно должен быть заключен в цикле для предотвращения следующих уязвимостей:
Thread in the middle: третий поток может получить блокировку на совместно используемый объект в течение интервала между отправленным уведомлением и возобновляющим приемом принимающего потока. Этот третий поток может изменить состояние объекта, оставив его непоследовательным. Это состояние времени проверки, времени использования (TOCTOU).
Вредоносные уведомления: случайное или злонамеренное уведомление может быть получено, когда предикат условия является ложным. Такое уведомление отменяет метод wait ().
Уведомление о недопустимости: порядок, в котором потоки выполняются после получения сигнала notifyAll (), не указан. Следовательно, несвязанный поток может начать выполнение и обнаружить, что предикат его условия выполняется. Следовательно, он может возобновить выполнение, несмотря на то, что ему необходимо оставаться бездействующим.
Подчиненные пробуждения. Некоторые реализации Java Virtual Machine (JVM) уязвимы для ложных пробуждений, которые приводят к тому, что ожидание потоков просыпается даже без уведомления.
По этим причинам программы должны проверять предикат условия после возврата метода wait (). Цикл while - лучший выбор для проверки предиката условия как до, так и после вызова wait ().
Аналогичным образом, метод await () интерфейса Condition также должен быть вызван внутри цикла. В соответствии с Java API, Условие интерфейса
При ожидании условия, допускается «ложное пробуждение», в общем, как уступка семантике базовой платформы. Это практически не влияет на большинство прикладных программ, поскольку условие всегда следует ждать в цикле, проверяя предикат состояния, которого ждут. Реализация бесплатна для устранения возможности ложных пробуждений, но рекомендуется, чтобы программисты приложений всегда предполагали, что они могут встречаться, и поэтому всегда ждут в цикле.
blockquote>Новый код должен использовать java. util.concurrent.locks. Утилиты параллелизма вместо механизма wait / notify. Однако устаревший код, соответствующий другим требованиям этого правила, может зависеть от механизма ожидания / уведомления.
Пример несовместимого кода Этот пример несовместимого кода вызывает метод wait () внутри традиционного блока if и после проверки уведомления не проверяется послесловие. Если уведомление было случайным или злонамеренным, поток может просыпаться преждевременно.
synchronized (object) { if (<condition does not hold>) { object.wait(); } // Proceed when condition holds }
Соответствующее решение Это совместимое решение вызывает метод wait () из цикла while, чтобы проверить состояние как до, так и после call to wait ():
synchronized (object) { while (<condition does not hold>) { object.wait(); } // Proceed when condition holds }
Вызов метода java.util.concurrent.locks.Condition.await () также должен быть заключен в аналогичный цикл.
Ответ на документацию для Object.wait (long milis)
Нить также может проснуться без уведомления, прерывания или тайминга, так что - вызвало ложное пробуждение. Хотя это редко случается на практике, приложения должны защищать его, проверяя условие, которое должно было вызвать пробуждение потока, и продолжая ждать, если условие не будет выполнено. Другими словами, ожидания всегда должны встречаться в циклах, таких как:
blockquote>synchronized (obj) { while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition }
(дополнительную информацию по этому вопросу см. В разделе 3.2.3 в статье «Параллельные действия Дуга Ли» Программирование на Java (второе издание) »(Addison-Wesley, 2000) или Пункт 50 в« Руководстве по эффективному языку программирования Java »Джошуа Блоха (Addison-Wesley, 2001).
blockquote>
Я думаю, что получил ответ @Gray.
Позвольте мне перефразировать это для новичков, подобных мне, и попросить экспертов исправить меня, если я ошибаюсь.
Потребитель синхронизированный блок ::
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
Синхронизируемый блок производителя ::
synchronized(queue) {
// producer produces inside the queue
queue.notify();
}
Предположим, что в данном порядке выполняется следующее:
1) потребитель №2 попадает в потребительский блок synchronized
и ждет, когда очередь пуста.
2) Теперь производитель получает блокировку на queue
и вставляет внутри очереди и вызывает notify ().
Теперь либо потребитель # 1 может быть выбран для запуска, который ждет блокировки queue
для входа в блок synchronized
в первый раз
или
потребитель # 2 можно выбрать для запуска.
3) скажем, что потребитель # 1 выбран для продолжения выполнения. Когда он проверяет условие, он будет правдой, и он remove()
из очереди.
4) скажем, что потребитель №2 идет от того места, где он остановил свое выполнение (строка после wait()
метод). Если условие «while» не существует (вместо условия if
), он просто перейдет к вызову remove()
, что может привести к возникновению исключения / неожиданного поведения.