Кто-нибудь может объяснить нить мониторов и ждать? - PullRequest
38 голосов
/ 22 октября 2008

Кто-то на работе только что спросил, почему стоит завернуть ожидание в синхронизированный файл.

Честно говоря, я не вижу причин. Я понимаю, что говорят Javadocs - что поток должен быть владельцем монитора объекта, но почему? Какие проблемы это мешает? (И если это действительно необходимо, почему метод wait не может получить сам монитор?)

Я ищу довольно глубокое объяснение, почему или, может быть, ссылку на статью. Я не смог найти его в быстром Google.

О, также, как сравнивается thread.sleep?

edit: Отличный набор ответов - я действительно хотел бы выбрать больше одного, потому что все они помогли мне понять, что происходит.

Ответы [ 6 ]

14 голосов
/ 22 октября 2008

Здесь уже много хороших ответов. Но просто хочу упомянуть здесь, что другое, что ДОЛЖНО ДЕЛАТЬ при использовании wait (), - это делать это в цикле, зависящем от состояния, которого вы ожидаете, в случае, если вы видите ложные пробуждения, что, по моему опыту, действительно происходит.

Чтобы дождаться, пока какой-нибудь другой поток изменит условие на true, и уведомит:

synchronized(o) {
  while(! checkCondition()) {
    o.wait();
  }
}

Конечно, в наши дни я бы порекомендовал просто использовать новый объект Condition, поскольку он более понятен и имеет больше функций (например, разрешение нескольких условий на блокировку, возможность проверки длины очереди ожидания, более гибкий график / прерывание и т. Д.) ).

 Lock lock = new ReentrantLock();
 Condition condition = lock.newCondition();
 lock.lock();
 try {
   while (! checkCondition()) {
     condition.await();
   }
 } finally {
   lock.unlock();
 }

}

5 голосов
/ 22 октября 2008

Ожидание отдает монитор, поэтому вы должны иметь его, чтобы отказаться от него. Уведомить также должен иметь монитор.

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

Если вы сделали такую ​​функцию:

public void synchWait() {
   syncronized { wait(); }
}

У вас не будет монитора, когда вернется ожидание - вы можете получить его, но в следующий раз вы можете его не получить.

5 голосов
/ 22 октября 2008

Ему необходим собственный монитор, поскольку цель wait () - освободить монитор и позволить другим потокам получить монитор для выполнения собственной обработки. Цель этих методов (ожидание / уведомление) состоит в том, чтобы координировать доступ к синхронизированным блокам кода между двумя потоками, которые требуют друг друга для выполнения некоторой функциональности. Дело не просто в том, чтобы убедиться, что доступ к структуре данных является потокобезопасным, а в том, чтобы координировать события между несколькими потоками.

Классическим примером может быть случай производителя / потребителя, когда один поток помещает данные в очередь, а другой поток потребляет данные. Поток-потребитель всегда будет требовать от монитора доступа к очереди, но освободит монитор, когда очередь будет пустой. Поток производителя получит доступ для записи в поток только тогда, когда потребитель больше не обрабатывает. Он будет уведомлять потребительский поток, как только он поместит больше данных в очередь, чтобы он мог восстановить монитор и снова получить доступ к очереди.

4 голосов
/ 22 октября 2008

Если объект не владеет монитором объекта при вызове Object.wait (), он не сможет получить доступ к объекту для настройки прослушивателя уведомлений до тех пор, пока монитор не будет освобожден. Вместо этого он будет рассматриваться как поток, пытающийся получить доступ к методу синхронизированного объекта.

Или, иначе говоря, разницы между:

нет
public void doStuffOnThisObject()

и следующий метод:

public void wait()

Оба метода будут заблокированы до тех пор, пока не будет отпущен монитор объекта. Это функция в Java, которая предотвращает обновление состояния объекта более чем одним потоком. У него просто непредвиденные последствия для метода wait ().

Предположительно, метод wait () не синхронизирован, поскольку это может создать ситуации, когда Thread имеет несколько блокировок на объекте. (См. Спецификации языка Java / Блокировка для получения дополнительной информации об этом.) Многократные блокировки являются проблемой, поскольку метод wait () отменяет только одну блокировку. Если бы метод был синхронизирован, это гарантировало бы, что только блокировка метода будет отменена, но при этом потенциальная внешняя блокировка будет отменена. Это создаст в коде взаимоблокировку.

Чтобы ответить на ваш вопрос о Thread.sleep (), Thread.sleep () не гарантирует, что какое бы условие вы не ожидали, было выполнено. Использование Object.wait () и Object.notify () позволяет программисту вручную реализовать блокировку. Потоки разблокируются после отправки уведомления о том, что условие выполнено. например Чтение с диска завершено, и данные могут быть обработаны потоком. Thread.sleep () потребует от программиста опроса, если условие выполнено, а затем возвратится в спящий режим, если оно не выполнено.

3 голосов
/ 23 июля 2009

В основном ожидание выполняется, если есть условие, что очередь пуста.

If(queue is empty)
     queue.wait();

Допустим, очередь пуста. В случае, если текущий поток имеет приоритет после проверки очереди, тогда, если другой поток добавляет несколько элементов в очередь, текущий поток не будет знать и будет ждать государство. Это неверно. Поэтому у нас должно быть что-то вроде

Synchornized(queue)
{
   if(queue is empty)
          queue.wait();
}

Теперь давайте рассмотрим, что если они заставят себя ждать как синхронизированные. Как уже упоминалось в одном из комментариев, он снимает только одну блокировку. Это означает, что если бы wait () был синхронизирован в вышеприведенном коде, была бы снята только одна блокировка. Подразумевает, что текущий поток пойдет на ожидание с блокировкой очереди.

3 голосов
/ 22 октября 2008

Вот мое понимание того, почему ограничение является обязательным требованием. Я основываю это на реализации монитора C ++, которую я сделал некоторое время назад, комбинируя мьютекс и переменную условия.

В системе mutex + condition_variable = monitor вызов wait переводит переменную условия в состояние ожидания и освобождает мьютекс. Переменная условия является общим состоянием, поэтому ее необходимо заблокировать, чтобы избежать состязаний между потоками, которые хотят ждать, и потоками, которые хотят уведомить. Вместо введения еще одного мьютекса для блокировки его состояния используется существующий мьютекс. В Java мьютекс корректно блокируется, когда ожидающий поток владеет монитором.

...