← Назад к вопросам
Как работают методы 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 гораздо удобнее