Может ли кто-нибудь объяснить мониторы потоков и подождать?

StackOverflow https://stackoverflow.com/questions/226455

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

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

public void doStuffOnThisObject()

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

public void wait()

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

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

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

Другие советы

Здесь уже много хороших ответов.Но здесь просто хочу упомянуть, что другое ДОЛЖНО СДЕЛАТЬ при использовании 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();
 }

}

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

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

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

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

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

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

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

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

В системе mutex + condition_variable = monitor

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

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

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

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

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

scroll top