Часто этот вопрос формулируется как задача
Producer-сonsumer. Эту задачу и практические задачи на многопоточность вообще при возможности лучше реализовывать на
высокоуровневых примитивах синхронизации. Другой подход – воспользоваться также низкоуровневой, но
оптимистической блокировкой на
compareAndSet. Но обычно использование
notify/wait (пессимистическая блокировка) – условие этого задания, то есть требуется
реализовать уже существую
BlockingQueue.
Эти методы вместе с
synchronized – самый низкий уровень пессимистических блокировок в Java, использующийся внутри реализации примитивов синхронизации. Еще с Java 5 в непосредственном использовании этих методов
нет необходимости, но теоретические знания всё еще часто спрашивают на интервью.
Чтобы вызывать эти методы у объекта, необходимо чтобы был захвачен его монитор (т.е. нужно быть внутри synchronized-блока на этом объекте). В противном случае будет выброшено
IllegalMonitorStateException. Так что для полного ответа нужно понимать, как работает monitor lock (блок
synchronized).
Вызов
wait тормозит текущий поток на ожидание на этом объекте и отпускает его монитор. Исполнение продолжится, когда другой поток вызовет
notify и отпустит блокировку монитора. Если на объекте ожидают несколько потоков,
notify разбудит один случайный,
notifyAll - все сразу.
В теории, ожидание
wait может быть прервано без вызова
notify, по желанию JVM (
spurious wakeup). На практике это бывает
крайне редко, но нужно страховаться и после вызова
wait добавлять дополнительную проверку условия завершения ожидания.
Еще два нештатных случая завершения
wait – прерывание потока извне и таймаут ожидания. В случае прерывания выбрасывается
InterruptedException. Для таймаута нужно указать время ожидания параметрами метода
wait. Значение 0 проигнорируется.
Различные проблемы реализации блокировок рассмотрены в
Java Concurrency in Practice 14.1.3, 14.2. Для желающих разобраться, как блокировки работают в кишках JVM, написана
статья на хабре.