Как использовать методы wait(), notify() и notifyAll() в Java?

«Как использовать методы wait(), notify() и notifyAll() в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Методы wait(), notify() и notifyAll() определены в классе Object и используются для межпоточной синхронизации (cooperative threading). Они должны вызываться внутри синхронизированного блока или метода.

Принцип работы:

  1. Поток захватывает монитор объекта (через synchronized).
  2. Вызов wait() освобождает монитор и переводит текущий поток в состояние ожидания.
  3. Другой поток, захватив тот же монитор, вызывает notify() (разбудить один случайный поток) или notifyAll() (разбудить все потоки).
  4. Пробужденный поток пытается перезахватить монитор и продолжает выполнение после вызова wait().

Пример шаблона "Producer-Consumer":

class SharedBuffer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity;

    public SharedBuffer(int capacity) { this.capacity = capacity; }

    // Метод производителя
    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == capacity) { // Всегда используйте цикл для проверки условия!
            wait(); // Ждет, пока потребитель не освободит место
        }
        queue.add(value);
        System.out.println("Produced: " + value);
        notifyAll(); // Уведомляет ожидающих потребителей (и производителей)
    }

    // Метод потребителя
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) { // Проверка в цикле на spurious wakeup
            wait(); // Ждет, пока производитель не добавит данные
        }
        int value = queue.poll();
        System.out.println("Consumed: " + value);
        notifyAll(); // Уведомляет ожидающих производителей
        return value;
    }
}

Ключевые правила:

  • Всегда вызывайте эти методы внутри synchronized блока, владея монитором того же объекта.
  • Всегда проверяйте условие ожидания в цикле while, а не в if. Это защищает от ложных пробуждений (spurious wakeups).
  • Предпочитайте notifyAll() для простоты, если нет строгих требований к производительности.
  • В современном коде часто лучше использовать высокоуровневые средства из java.util.concurrent (например, BlockingQueue, Locks с Condition).