А wait()
имеет смысл только тогда, когда есть notify()
, так что это всегда о связи между потоками, и для правильной работы требуется синхронизация. Можно утверждать, что это должно быть неявным, но это не очень поможет по следующей причине:
Семантически, вы никогда не просто wait()
. Вам нужно какое-то условие, чтобы быть насыщенным, и если это не так, вы ждете, пока оно не будет выполнено. Так что вы действительно делаете
if(!condition){
wait();
}
Но условие устанавливается отдельным потоком, поэтому для правильной работы вам нужна синхронизация.
Еще пара вещей не так с этим, потому что если ваш поток перестал ждать, это не означает, что искомое условие верно:
Вы можете получить ложные пробуждения (это означает, что поток может проснуться от ожидания, даже не получив уведомления), или
Условие может быть установлено, но третий поток снова делает условие ложным к тому моменту, когда ожидающий поток пробуждается (и повторно запрашивает монитор).
Чтобы иметь дело с этими случаями, то, что вам действительно нужно, это всегда несколько вариантов:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Еще лучше, не связывайтесь с примитивами синхронизации вообще и работайте с абстракциями, предлагаемыми в java.util.concurrent
пакетах.
Thread.interrupted()
.Давайте проиллюстрируем, с какими проблемами мы столкнемся, если
wait()
будем вызывать вне синхронизированного блока, на конкретном примере .Предположим, мы должны были реализовать очередь блокировки (я знаю, в API уже есть такая очередь :)
Первая попытка (без синхронизации) может выглядеть примерно так
Это то, что потенциально может произойти:
Потребительский поток звонит
take()
и видит, чтоbuffer.isEmpty()
.Прежде чем потребительский поток переходит к вызову
wait()
, продюсерский поток приходит и вызывает полныйgive()
, то естьbuffer.add(data); notify();
Поток потребителя теперь будет вызывать
wait()
(и пропустить толькоnotify()
что вызванный).Если не повезет, поток производителя не будет производить больше
give()
в результате того, что поток потребителя никогда не просыпается, и у нас есть тупик.Как только вы поймете проблему, решение станет очевидным: используйте,
synchronized
чтобы убедиться, чтоnotify
никогда не вызывается междуisEmpty
иwait
.Не вдаваясь в детали: эта проблема синхронизации является универсальной. Как указывает Майкл Боргвардт, ожидание / уведомление - это все о связи между потоками, поэтому у вас всегда будет состояние гонки, подобное описанному выше. Вот почему применяется правило «только ждать внутри синхронизированного».
Абзац по ссылке, опубликованной @Willie, резюмирует это довольно хорошо:
Предикат, с которым производитель и потребитель должны договориться, находится в приведенном выше примере
buffer.isEmpty()
. Соглашение разрешается путем обеспечения того, что ожидание и уведомление выполняютсяsynchronized
блоками.Этот пост был переписан как статья здесь: Java: почему нужно вызывать wait в синхронизированном блоке
источник
return buffer.remove();
в то время как блок, но послеwait();
, это работает?wait
возврата.Thread.currentThread().wait();
вmain
функции, окруженной try-catch дляInterruptedException
. Безsynchronized
блока это дает мне то же исключениеIllegalMonitorStateException
. Что заставляет это достигнуть незаконного государства теперь? Это работает внутриsynchronized
блока, хотя.@ Роллербол прав.
wait()
Называются, так что нить может подождать некоторое условие происходит , когда этоwait()
происходит вызов, поток вынужден отказаться от своего замка.Чтобы что-то бросить, нужно сначала владеть этим. Поток должен владеть блокировкой в первую очередь. Отсюда необходимость вызывать его внутри
synchronized
метода / блока.Да, я согласен со всеми приведенными выше ответами относительно возможных повреждений / несоответствий, если вы не проверили условие в
synchronized
методе / блоке. Однако, как указал @ shrini1000, простой вызовwait()
в синхронизированном блоке не предотвратит возникновение этой несогласованности.Вот хорошее чтение ..
источник
Проблема, которая может возникнуть, если вы не выполняете синхронизацию,
wait()
заключается в следующем:makeChangeOnX()
и проверяет условие while, и оноtrue
(x.metCondition()
возвращаетсяfalse
, значитx.condition
естьfalse
), так что оно попадет внутрь него. Тогда как раз передwait()
методом, другой поток идет кsetConditionToTrue()
и устанавливаетx.condition
кtrue
иnotifyAll()
.wait()
метод (не затронутый тем,notifyAll()
что произошло несколько мгновений назад). В этом случае 1-й поток будет ждать выполнения другого потокаsetConditionToTrue()
, но это может не повториться.источник
Мы все знаем, что методы wait (), notify () и notifyAll () используются для межпоточных коммуникаций. Чтобы избавиться от пропущенного сигнала и ложных проблем с пробуждением, ожидающий поток всегда ожидает некоторых условий. например-
Затем уведомляющий поток устанавливает переменную wasNotified в true и уведомляет.
Каждый поток имеет свой локальный кэш, поэтому все изменения сначала записываются туда, а затем постепенно перемещаются в основную память.
Если бы эти методы не вызывались в синхронизированном блоке, переменная wasNotified не была бы сброшена в основную память и была бы в локальном кеше потока, поэтому ожидающий поток будет продолжать ждать сигнала, хотя он был сброшен уведомляющим потоком.
Чтобы устранить проблемы такого типа, эти методы всегда вызываются внутри синхронизированного блока, который гарантирует, что при запуске синхронизированного блока все будет считано из основной памяти и будет сброшено в основную память перед выходом из синхронизированного блока.
Спасибо, надеюсь, это проясняет.
источник
Это в основном связано с аппаратной архитектурой (то есть ОЗУ и кэш- памятью ).
Если вы не используете
synchronized
вместе сwait()
илиnotify()
, другой поток может войти в тот же блок, вместо того, чтобы ждать, пока монитор войдет в него. Более того, когда, например, обращаясь к массиву без синхронизированного блока, другой поток может не увидеть изменения к нему ... фактически другой поток не увидит никаких изменений к нему, когда у него уже есть копия массива в кэше уровня x ( aka 1-й / 2-й / 3-й уровень кэшей) потока, обрабатывающего ядро ЦП.Но синхронизированные блоки - это только одна сторона медали: если вы фактически получаете доступ к объекту в синхронизированном контексте из несинхронизированного контекста, объект все равно не будет синхронизирован даже внутри синхронизированного блока, поскольку он содержит собственную копию объект в своем кеше. Я писал об этих проблемах здесь: https://stackoverflow.com/a/21462631 и Когда блокировка содержит неконечный объект, может ли ссылка на объект все еще быть изменена другим потоком?
Кроме того, я убежден, что кэши уровня x ответственны за большинство невоспроизводимых ошибок времени выполнения. Это потому, что разработчики обычно не изучают низкоуровневые вещи, например, как работает процессор или как иерархия памяти влияет на работу приложений: http://en.wikipedia.org/wiki/Memory_hierarchy
Остается загадкой, почему классы программирования не начинаются с иерархии памяти и архитектуры ЦП. «Привет мир» здесь не поможет. ;)
источник
прямо из этого руководства по Java-оракулу:
источник
Когда вы вызываете notify () из объекта t, java уведомляет определенный метод t.wait (). Но, как Java ищет и уведомляет конкретный метод ожидания.
Java смотрит только на синхронизированный блок кода, который был заблокирован объектом t. Java не может искать весь код, чтобы уведомить конкретный t.wait ().
источник
согласно документам:
wait()
Метод просто означает, что он снимает блокировку с объекта. Таким образом, объект будет заблокирован только внутри синхронизированного блока / метода. Если поток находится вне блока синхронизации, значит, он не заблокирован, если он не заблокирован, что бы вы освободили для объекта?источник
Ожидание потока на объекте мониторинга (объект, используемый блоком синхронизации). Может быть n номеров объекта мониторинга во всем пути одного потока. Если поток ожидает за пределами блока синхронизации, тогда объект мониторинга отсутствует, а также другой поток уведомляет о доступе к объекту мониторинга, так как бы поток вне блока синхронизации мог узнать, что он был уведомлен. Это также одна из причин того, что wait (), notify () и notifyAll () находятся в классе объекта, а не в классе потока.
По сути, объект мониторинга является общим ресурсом для всех потоков, а объекты мониторинга могут быть доступны только в блоке синхронизации.
источник