Подтвердить что ты не робот

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

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

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

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

О, также, как выглядит thread.sleep?

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

4b9b3361

Ответ 1

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

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

public void doStuffOnThisObject()

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

public void wait()

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

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

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

Ответ 2

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

}

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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