Java Threads on wait () теряет блокировку, но остается внутри синхронизированного блока [duplicate]

Я попытаюсь объяснить вам статическую вещь. Прежде всего статические переменные не принадлежат ни одному конкретному экземпляру класса. Они распознаются с именем класса. Статические методы снова не относятся к какому-либо конкретному экземпляру. Они могут получить доступ только к статическим переменным. Представьте, что вы вызываете MyClass.myMethod (), а myMethod - это статический метод. Если вы используете нестатические переменные внутри метода, как, черт возьми, он знает, какие переменные использовать? Вот почему вы можете использовать из статических методов только статические переменные. Повторяю, они НЕ принадлежат ни одному конкретному экземпляру.

50
задан oxbow_lakes 24 June 2009 в 13:50
поделиться

8 ответов

Поскольку wait и notify используются для реализации [condition variables] ( http://en.wikipedia.org/wiki/Monitor_ (синхронизация) #Blocking_condition_variables) , и поэтому вам нужно проверить, определенный предикат, который вы ожидаете, верен, прежде чем продолжить.

5
ответ дан Aaron Maenpaa 24 August 2018 в 07:54
поделиться
  • 1
    kd304 является правильным - дело не только в том, что условие может быть не выполнено - это тот факт, что поток может ложно проснуться от ожидания – oxbow_lakes 24 June 2009 в 13:51
  • 2
    @oxbow_lakes. Что касается потока, ожидающего состояния, то нет никакой реальной разницы между ложным пробуждением и уведомлением, предназначенным для сигнализации о другом состоянии. В любом случае, вы должны проверить свой предикат. – Aaron Maenpaa 24 June 2009 в 15:19

Из вашего вопроса:

Я прочитал, что мы должны всегда вызывать wait () из цикла:

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

По сути, поток возобновляется без видимых причин.

Из-за этой удаленной возможности , Oracle рекомендует, чтобы вызовы wait () должны выполняться в цикле, который проверяет условие ожидания потока.

1
ответ дан Adil 24 August 2018 в 07:54
поделиться
  • 1
    Этот ответ неверен. Это не риск побочных пробуждений, вот почему 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. В зависимости от потребностей вашего бизнеса вы можете выйти или подавить исключение и продолжить ожидание.

59
ответ дан akarnokd 24 August 2018 в 07:54
поделиться
  • 1
    Это правильный ответ. Документация для ожидания: java.sun.com/javase/6/docs/api/java/lang/Object.html#wait (long) ... на самом деле описывает, почему вам нужно положить ее в петля - ложные пробуждения. Прочитал ли он? – Andrew Duffy 24 June 2009 в 13:39
  • 2
    Ах, «побочные пробуждения». Не удалось вспомнить имя. – akarnokd 24 June 2009 в 13:47
  • 3
    Вы должны обновить свой пример кода, чтобы обработать ThreadInterruptedException в ожидании. Тогда это будет соответствовать вашему ответу. – Robin 24 June 2009 в 13:56
  • 4
    Не знаю. Добавление try-catch для InterruptedException казалось бессмысленным, поскольку это зависит от бизнес-логики, что вы хотите сделать в случае прерываний потоков. Возможно, вы просто выйдете или немедленно подавите его внутри цикла. – akarnokd 24 June 2009 в 14:13
  • 5
    @Geek должен принять ответ. – SK9 27 August 2013 в 09:25

Почему wait () всегда следует вызывать внутри цикла

. Основная причина, по которой петли 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 по-прежнему пуст из-за этого состояния гонки.

Это хорошо документировано. Вот веб-страница, которую я создал некоторое время назад, которая подробно объясняет условие гонки и имеет некоторый пример кода.

8
ответ дан Gray 24 August 2018 в 07:54
поделиться
  • 1
    Разве это не должно быть while (queue.isEmpty()) без !? – user104309 29 October 2016 в 14:17
  • 2
    Да, вы правы. Исправлена. Благодарю. – Gray 31 October 2016 в 16:09
  • 3
    почему потребитель 1 может считаться удаляемым из очереди, если потребитель 2 не вышел из синхронизированного блока. уведомление в любом случае собирается уведомить потребителя 2, а потребитель 1 останется бездействующим. – jayendra bhatt 4 June 2017 в 17:13
  • 4
    Потребитель № 1 остается бездействующим, но все еще ждет блокировки. Когда запрос № 2 получает уведомление, он не запускается сразу, потому что ему нужна блокировка, поэтому он переходит в ту же очередь ожидания, но позади # 1 @jayendrabhatt. – Gray 4 June 2017 в 21:44
  • 5
    Кто-нибудь хочет объяснить нижний план? – Gray 5 June 2017 в 12:27

Может быть, только один работник ждет, пока условие станет истинным.

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

8
ответ дан Philipp 24 August 2018 в 07:54
поделиться

При использовании механизма 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, Условие интерфейса

При ожидании условия, допускается «ложное пробуждение», в общем, как уступка семантике базовой платформы. Это практически не влияет на большинство прикладных программ, поскольку условие всегда следует ждать в цикле, проверяя предикат состояния, которого ждут. Реализация бесплатна для устранения возможности ложных пробуждений, но рекомендуется, чтобы программисты приложений всегда предполагали, что они могут встречаться, и поэтому всегда ждут в цикле.

Новый код должен использовать 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 () также должен быть заключен в аналогичный цикл.

3
ответ дан Sameer Patel 24 August 2018 в 07:54
поделиться

Ответ на документацию для Object.wait (long milis)

Нить также может проснуться без уведомления, прерывания или тайминга, так что - вызвало ложное пробуждение. Хотя это редко случается на практике, приложения должны защищать его, проверяя условие, которое должно было вызвать пробуждение потока, и продолжая ждать, если условие не будет выполнено. Другими словами, ожидания всегда должны встречаться в циклах, таких как:

 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).

33
ответ дан Tadeusz Kopec 24 August 2018 в 07:54
поделиться

Я думаю, что получил ответ @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(), что может привести к возникновению исключения / неожиданного поведения.

6
ответ дан user104309 24 August 2018 в 07:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: