wait, notify и notifyAll

Эти методы принадлежат Object и работают с wait set конкретного монитора.

Контракт

  1. Поток обязан владеть тем же монитором, иначе IllegalMonitorStateException.
  2. wait() атомарно помещает поток в ожидание и освобождает этот монитор.
  3. После сигнала поток не продолжает сразу: он снова конкурирует за монитор.
  4. Условие проверяют в while, потому что возможны ложные пробуждения и другой поток может изменить состояние до повторного захвата.
final class OneSlot<T> {
    private T value;
    private boolean full;
 
    public synchronized void put(T next) throws InterruptedException {
        while (full) {
            wait();
        }
        value = next;
        full = true;
        notifyAll();
    }
 
    public synchronized T take() throws InterruptedException {
        while (!full) {
            wait();
        }
        T result = value;
        value = null;
        full = false;
        notifyAll();
        return result;
    }
}

notify() будит один произвольно выбранный поток. Если на одном мониторе ждут разные условия, можно разбудить неподходящего и получить зависание. notifyAll() будит всех, после чего каждый перепроверяет свой predicate; это надежнее, хотя создает лишнюю конкуренцию.

wait() освобождает только монитор, на котором вызван. Другие мониторы и Lock остаются захваченными. Interrupted waiting завершится InterruptedException.

На практике готовая BlockingQueue, CountDownLatch или Condition чаще выражает протокол точнее.

На собеседовании

  • Почему wait() только внутри synchronized? Нужно атомарно проверить условие и заснуть без потерянного сигнала.
  • Почему while, а не if? Ложные пробуждения и конкуренция после сигнала.
  • Продолжает ли поток сразу после notify()? Нет, сначала снова захватывает монитор.
  • Освобождает ли wait() все блокировки? Нет, только монитор вызова.
  • Чем notifyAll() безопаснее? Все ожидающие перепроверяют свои условия.