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

Зачем нужен wait?

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

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

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

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

Ответ

Метод wait() — это один из самых важных инструментов для синхронизации потоков в Java. Он нужен для организации взаимодействия между потоками через механизм "монитора" (встроенная блокировка объекта).

Основная идея

wait() заставляет текущий поток ждать, пока другой поток не уведомит его через notify() или notifyAll(). Это основа паттерна Producer-Consumer.

Зачем это нужно

1. Избегание busy-waiting (активного ожидания)

// ❌ Плохо: поток жрет CPU в цикле
while (queue.isEmpty()) {
    // spin loop — отвратительно для производительности
}
Item item = queue.poll();

// ✅ Хорошо: поток спит, пока нет данных
synchronized (queue) {
    while (queue.isEmpty()) {
        queue.wait(); // отдает CPU другим потокам
    }
    Item item = queue.poll();
}

2. Минимизация использования процессора

Когда поток вызывает wait(), он освобождает монитор объекта и засыпает. Процессор не тратит время на проверку условия в цикле.

3. Синхронизация между потоками

Это основной механизм для организации взаимодействия:

  • Поток A ждет событие
  • Поток B уведомляет о событии
  • Поток A пробуждается и продолжает работу

Как это работает

public class ProducerConsumer {
    private Queue<String> buffer = new LinkedList<>();
    private final int MAX_SIZE = 10;

    // Producer
    public synchronized void produce(String item) throws InterruptedException {
        while (buffer.size() == MAX_SIZE) {
            wait(); // ждем, пока Consumer освободит место
        }
        buffer.offer(item);
        notifyAll(); // уведомляем Consumer, что есть данные
    }

    // Consumer
    public synchronized String consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // ждем, пока Producer добавит данные
        }
        String item = buffer.poll();
        notifyAll(); // уведомляем Producer, что место освободилось
        return item;
    }
}

Важные особенности

1. Вызов только из synchronized блока

// ❌ Ошибка: IllegalMonitorStateException
object.wait();

// ✅ Правильно
synchronized (object) {
    object.wait();
}

2. Вариант wait() без параметров

object.wait(); // ждет бесконечно, пока кто-то не вызовет notify()

3. wait() с таймаутом

object.wait(1000); // ждет максимум 1 секунду
// Вернет контроль, даже если никто не вызвал notify()

4. Проверка условия в цикле

// ❌ Неправильно: проверяем один раз
if (buffer.isEmpty()) {
    wait();
}
Item item = buffer.poll(); // NPE если буфер все еще пуст!

// ✅ Правильно: проверяем в цикле
while (buffer.isEmpty()) {
    wait(); // может быть spurious wakeup — ложное пробуждение
}
Item item = buffer.poll();

Spurious wakeup — это явление, когда поток просыпается без вызова notify(). Может случиться на некоторых платформах, поэтому всегда используйте цикл.

notify() vs notifyAll()

notify()

notify(); // пробуждает ОДНОГО ждущего потока (непредсказуемо какого)

notifyAll()

notifyAll(); // пробуждает ВСЕХ ждущих потоков

Когда какой использовать:

  • notifyAll() — когда несколько потоков могут быть заинтересованы в событии (безопаснее, но медленнее)
  • notify() — когда ровно один поток должен проснуться (редко, требует аккуратности)

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

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

1. BlockingQueue

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("item"); // Producer
String item = queue.take(); // Consumer автоматически ждет

2. CountDownLatch

CountDownLatch latch = new CountDownLatch(1);
latch.await(); // ждет, пока latch.countDown() не вернет 0
latch.countDown(); // уведомляет

3. Condition (из java.util.concurrent.locks)

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
condition.await(); // ждет
lock.unlock();

lock.lock();
condition.signalAll(); // уведомляет
lock.unlock();

Практический пример: простой тред-пул

public class SimpleThreadPool {
    private Queue<Runnable> tasks = new LinkedList<>();
    private boolean shutdown = false;

    public void submit(Runnable task) throws InterruptedException {
        synchronized (tasks) {
            tasks.offer(task);
            tasks.notifyAll(); // уведомляем рабочие потоки
        }
    }

    public void startWorker() {
        new Thread(() -> {
            while (true) {
                Runnable task = null;
                synchronized (tasks) {
                    while (tasks.isEmpty() && !shutdown) {
                        try {
                            tasks.wait(); // ждем задач
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    if (tasks.isEmpty()) return; // shutdown
                    task = tasks.poll();
                }
                if (task != null) {
                    task.run();
                }
            }
        }).start();
    }
}

Вывод

wait() нужен для эффективной синхронизации потоков без активного ожидания. Это фундаментальный механизм многопоточности в Java, который позволяет потокам не жрать CPU, пока они ждут. Хотя в современной Java есть более удобные альтернативы (BlockingQueue, CountDownLatch, Condition), понимание wait()/notify() критично для глубокого понимания многопоточности.

Зачем нужен wait? | PrepBro