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

Что такое wait?

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

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

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

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

# wait() в Java — синхронизация потоков

wait() — это метод, который позволяет потоку приостановиться и отпустить монитор объекта, ожидая, пока другой поток уведомит его о готовности продолжить работу. Это один из базовых механизмов синхронизации между потоками.

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

Метод wait() определён в классе Object, поэтому доступен всем объектам. Он работает в тесной связи с методами notify() и notifyAll().

// Синтаксис
public final void wait() throws InterruptedException
public final void wait(long timeoutMillis) throws InterruptedException
public final void wait(long timeoutMillis, int nanos) throws InterruptedException

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

synchronized (lockObject) {      // 1. Захватываем монитор
    while (!condition) {         // 2. Проверяем условие
        lockObject.wait();       // 3. Отпускаем монитор и ждём
        // 5. Другой поток вызвал notify() → просыпаемся
    }
    // 6. Снова захватили монитор
    // Выполняем работу
}

// В другом потоке:
synchronized (lockObject) {      // Захватываем тот же монитор
    condition = true;            // Меняем условие
    lockObject.notify();         // 4. Уведомляем ждущий поток
}

Ключевые моменты

1. wait() ВСЕГДА в synchronized блоке

// ❌ Неправильно — выбросит IllegalMonitorStateException
public void badWait() {
    try {
        object.wait();  // ERROR!
    } catch (InterruptedException e) {}
}

// ✅ Правильно
public void goodWait() {
    synchronized (object) {
        try {
            object.wait();
        } catch (InterruptedException e) {}
    }
}

2. wait() отпускает монитор

synchronized (object) {
    // Мы владеем монитором
    object.wait();
    // wait() отпускает монитор и блокируется
    // Другие потоки теперь могут захватить монитор
    // После notify() монитор снова захватывается
}

3. Всегда проверяй условие в while, не if

// ❌ Неправильно
synchronized (lock) {
    if (!isReady) {
        lock.wait();  // Может вернуться раньше, чем изменится isReady
    }
    // Используем isReady
}

// ✅ Правильно
synchronized (lock) {
    while (!isReady) {  // Перепроверяем условие после пробуждения
        lock.wait();
    }
    // Используем isReady
}

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

public class ProducerConsumer {
    private final Queue<Integer> buffer = new LinkedList<>();
    private final int capacity = 5;
    private final Object lock = new Object();
    
    // ПРОИЗВОДИТЕЛЬ
    public void produce(Integer value) throws InterruptedException {
        synchronized (lock) {
            // Ждём, пока буфер освободится
            while (buffer.size() == capacity) {
                System.out.println("[Producer] Буфер полный, жду...");
                lock.wait();  // Отпускаем монитор и ждём
            }
            
            buffer.add(value);
            System.out.println("[Producer] Добавил " + value + ", в буфере: " + buffer.size());
            
            // Уведомляем консумера, что данные готовы
            lock.notifyAll();
        }
    }
    
    // ПОТРЕБИТЕЛЬ
    public Integer consume() throws InterruptedException {
        synchronized (lock) {
            // Ждём, пока буфер не заполнится
            while (buffer.isEmpty()) {
                System.out.println("[Consumer] Буфер пуст, жду...");
                lock.wait();  // Отпускаем монитор и ждём
            }
            
            Integer value = buffer.poll();
            System.out.println("[Consumer] Получил " + value + ", в буфере: " + buffer.size());
            
            // Уведомляем производителя, что место освободилось
            lock.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 <= 10; i++) {
                    pc.produce(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        // Потребитель в отдельном потоке
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pc.consume();
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        producer.start();
        consumer.start();
    }
}

Три варианта wait()

1. wait() — бесконечное ожидание

synchronized (lock) {
    while (!condition) {
        lock.wait();  // Будет ждать, пока не вызовут notify()
    }
}

2. wait(long timeout) — ожидание с таймаутом

synchronized (lock) {
    while (!condition) {
        lock.wait(5000);  // Ждёт максимум 5 секунд
        // Если за 5 сек никто не вызовет notify() —
        // поток пробудится сам
        
        if (!condition) {
            System.out.println("Timeout! Условие не стало true");
        }
    }
}

3. wait(long timeoutMillis, int nanos) — точный таймаут

lock.wait(5000, 500000);  // 5 сек + 500000 наносекунд

notify() vs notifyAll()

// ❌ Использование notify() — опасно!
synchronized (lock) {
    data.add(item);
    lock.notify();  // Может пробудить неправильный поток
}

// ✅ Использование notifyAll() — безопаснее!
synchronized (lock) {
    data.add(item);
    lock.notifyAll();  // Пробуждает ВСЕ ждущие потоки
}

Почему notifyAll() лучше:

  • notify() пробуждает только ОДИН случайный поток
  • Он может быть не тем, который ждёт условие
  • notifyAll() гарантирует, что нужный поток проснётся
  • Все потоки перепроверяют условие и если оно false — ждут дальше

Spurious Wakeups — ложные пробуждения

// Поток может проснуться БЕЗ notify()!
// Поэтому ВСЕГДА используй while, не if

synchronized (lock) {
    // ❌ if (поле может быть ложное пробуждение):
    while (!condition) {  // ✅ while — перепроверяет условие
        lock.wait();
    }
}

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

Для новых проектов используй высокоуровневые инструменты вместо wait()/notify():

// ❌ Старый подход
Object lock = new Object();
synchronized (lock) {
    lock.wait();
}

// ✅ Современный подход (BlockingQueue)
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();nqueue.take();  // Внутри используется wait()

// ✅ Современный подход (CountDownLatch)
CountDownLatch latch = new CountDownLatch(1);
latch.await();  // Ждёт, пока счётчик не станет 0
latch.countDown();  // Уменьшает счётчик

// ✅ Современный подход (Condition)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
    condition.await();
} finally {
    lock.unlock();
}

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

  1. ✓ wait() всегда в synchronized блоке
  2. ✓ Всегда проверяй условие в while, не if
  3. ✓ Обрабатывай InterruptedException
  4. ✓ Используй notifyAll(), а не notify()
  5. ✓ Для новых проектов предпочитай BlockingQueue, Condition, CountDownLatch
Что такое wait? | PrepBro