← Назад к вопросам

Как работают методы wait() и notify()/notifyAll()?

2.0 Middle🔥 181 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы wait(), notify() и notifyAll() в Java

Эти методы — основа механизма синхронизации потоков в Java. Они позволяют потокам взаимодействовать друг с другом через монитор объекта (lock).

Основные концепции

Монитор (Lock) — это механизм, который предотвращает одновременный доступ нескольких потоков к объекту.

Очередь ожидания (Wait Set) — это список потоков, которые ждут на этом объекте.

Метод wait()

wait() — это метод, который отпускает монитор объекта и переводит поток в состояние ожидания.

Основные моменты:

  • Может быть вызван ТОЛЬКО внутри synchronized блока
  • Отпускает монитор объекта (другие потоки могут захватить его)
  • Поток переходит в состояние WAITING
  • Поток выйдет из wait() только если:
    • Другой поток вызвал notify() или notifyAll()
    • Прошло время (если использовался wait(timeout))
    • Произошло interrupt()
public synchronized void waitForSignal() throws InterruptedException {
    wait();  // отпускаем монитор и ждем
    // когда сюда вернемся, монитор будет снова захвачен
}

public synchronized void waitWithTimeout() throws InterruptedException {
    wait(1000);  // ждем максимум 1 секунду
}

Метод notify()

notify() — пробуждает ОДИН поток из очереди ожидания.

Основные моменты:

  • Должен быть вызван ВНУТРИ synchronized блока
  • Пробуждает СЛУЧАЙНЫЙ поток из очереди
  • Пробуженный поток будет ждать, пока текущий поток отпустит монитор
  • Если нет потоков на wait() — операция просто игнорируется
public synchronized void sendSignal() {
    notify();  // пробуждаем один поток
}

Метод notifyAll()

notifyAll() — пробуждает ВСЕ потоки из очереди ожидания.

Основные моменты:

  • Должен быть вызван ВНУТРИ synchronized блока
  • Пробуждает ВСЕ ожидающие потоки
  • Все пробуженные потоки конкурируют за монитор
  • Обычно более безопасен, чем notify()
public synchronized void sendSignalToAll() {
    notifyAll();  // пробуждаем все потоки
}

Практический пример: Producer-Consumer

public class ProducerConsumer {
    private int buffer;
    private boolean isEmpty = true;
    
    public synchronized void produce(int value) throws InterruptedException {
        while (!isEmpty) {
            wait();  // ждем, пока буфер будет пуст
        }
        buffer = value;
        isEmpty = false;
        System.out.println("Произведено: " + value);
        notifyAll();  // уведомляем потребителей
    }
    
    public synchronized int consume() throws InterruptedException {
        while (isEmpty) {
            wait();  // ждем, пока буфер будет наполнен
        }
        int value = buffer;
        isEmpty = true;
        System.out.println("Потреблено: " + value);
        notifyAll();  // уведомляем производителей
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();
        
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    pc.produce(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    pc.consume();
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        producer.start();
        consumer.start();
    }
}

Вывод:

Произведено: 1
Потреблено: 1
Произведено: 2
Потреблено: 2
Произведено: 3
Потреблено: 3
Произведено: 4
Потреблено: 4
Произведено: 5
Потреблено: 5

Жизненный цикл потока

1. RUNNABLE (исполняется)
      ↓
   synchronized (obj) {
      ↓
   obj.wait()  ← поток переходит в WAITING
      ↓
2. WAITING (ждет на очереди объекта)
      ↓
   другой поток вызывает notify()
      ↓
3. WAITING → конкурирует за монитор
      ↓
4. RUNNABLE (захватил монитор, продолжает работу)

Различие между wait() и sleep()

Параметрwait()sleep()
Отпускает мониторДаНет
ВызовТОЛЬКО в synchronizedВезде
Пробуждениеnotify() / notifyAll()Автоматически по времени
InterruptedExceptionДаДа
ИспользованиеСинхронизация потоковЗадержка выполнения

Классический пример: Очередь

public class Queue<T> {
    private java.util.LinkedList<T> list = new java.util.LinkedList<>();
    private int capacity;
    
    public Queue(int capacity) {
        this.capacity = capacity;
    }
    
    public synchronized void put(T value) throws InterruptedException {
        while (list.size() >= capacity) {
            wait();  // ждем, пока освободится место
        }
        list.add(value);
        System.out.println("Добавлено: " + value);
        notifyAll();  // уведомляем потребителей
    }
    
    public synchronized T get() throws InterruptedException {
        while (list.isEmpty()) {
            wait();  // ждем, пока появится элемент
        }
        T value = list.removeFirst();
        System.out.println("Получено: " + value);
        notifyAll();  // уведомляем производителей
        return value;
    }
}

Правило захвата монитора

Object obj = new Object();

synchronized (obj) {
    try {
        obj.wait();      // ОК
        obj.notify();    // ОК
        obj.notifyAll(); // ОК
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

// ЭТО ОШИБКА:
try {
    obj.wait();  // IllegalMonitorStateException!
} catch (InterruptedException e) {
    e.printStackTrace();
}

Современная альтернатива

Вместо wait()/notify() рекомендуется использовать высокоуровневые классы:

import java.util.concurrent.*;

// Вместо wait/notify используй BlockingQueue
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

queue.put(5);      // Автоматически ждет, если полная
int value = queue.take();  // Автоматически ждет, если пустая

Основные альтернативы:

  • BlockingQueue — для очередей
  • Condition из java.util.concurrent.locks
  • Semaphore — для контроля доступа
  • CountDownLatch — для синхронизации
  • CyclicBarrier — для барьеров

Важные моменты

  • wait() ВСЕГДА требует synchronized блока
  • notify() пробуждает ОДИН поток, notifyAll() пробуждает ВСЕ
  • Проверяй условие в цикле while, а не в if (spurious wakeups)
  • notifyAll() безопаснее, чем notify()
  • Современные классы из java.util.concurrent гораздо удобнее