Avatar ·

В чем разница между методами wait() и sleep() в Java? Примеры с кодом

Всем привет! Я начал изучать многопоточность в Java и запутался в методах wait() и sleep(). Вроде бы оба приостанавливают выполнение потока, но ведут себя по-разному. Помогите разобраться. У меня есть простой пример. Я создаю два потока: один производит данные, другой их потребляет. Использую sleep() для синхронизации: ```java class Producer extends Thread { private List buffer = new ArrayList<>(); public void run() { for (int i = 0; i < 10; i++) { buffer.add(i); System.out.println("Произведено: " + i); try { Thread.sleep(100); // Ждем, пока потребитель заберет } catch (InterruptedException e) {} } } public int getValue() { return buffer.remove(0); } } class Consumer extends Thread { private Producer producer; Consumer(Producer p) { producer = p; } public void run() { for (int i = 0; i < 10; i++) { int value = producer.getValue(); System.out.println("Потреблено: " + value); } } } ``` Этот код часто падает с исключением, потому что потребитель пытается забрать данные из пустого списка. Я слышал, что wait() решает такие проблемы, но не понимаю, как именно. Чем wait() отличается от sleep()? Где какой метод применять? Объясните на примерах, пожалуйста. <<>>

Основные различия между wait() и sleep() в Java

Методы wait() и sleep() действительно оба приостанавливают выполнение потока, но имеют принципиально разное назначение и поведение. Вот ключевые отличия:

  • Принадлежность к классам: sleep() — статический метод класса Thread, а wait() — метод класса Object
  • Синхронизация: wait() обязательно должен вызываться внутри синхронизированного блока (synchronized), иначе будет IllegalMonitorStateException. sleep() может вызываться где угодно
  • Освобождение блокировок: wait() освобождает монитор объекта (блокировку), позволяя другим потокам войти в синхронизированные блоки. sleep() не освобождает никакие блокировки
  • Пробуждение: sleep() просыпается автоматически по истечении таймаута. wait() можно разбудить вызовом notify() или notifyAll() из другого потока, либо по таймауту
  • Цель использования: sleep() — для паузы на фиксированное время. wait() — для межпотокового взаимодействия (Producer-Consumer паттерн)

Пример корректного использования wait() и notify()

Исправим ваш пример Producer-Consumer с помощью wait() и notify():

import java.util.LinkedList;
import java.util.Queue;

class SharedBuffer {
    private Queue<Integer> buffer = new LinkedList<>();
    private final int MAX_SIZE = 5;

    public synchronized void produce(int value) throws InterruptedException {
        while (buffer.size() == MAX_SIZE) {
            wait(); // Ждем, пока потребитель освободит место
        }
        buffer.add(value);
        System.out.println("Произведено: " + value);
        notify(); // Будим ожидающий поток-потребитель
    }

    public synchronized int consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // Ждем, пока производитель добавит данные
        }
        int value = buffer.poll();
        System.out.println("Потреблено: " + value);
        notify(); // Будим ожидающий поток-производитель
        return value;
    }
}

class Producer extends Thread {
    private SharedBuffer buffer;
    
    Producer(SharedBuffer b) { buffer = b; }
    
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                buffer.produce(i);
                Thread.sleep(100); // sleep() для имитации работы
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

class Consumer extends Thread {
    private SharedBuffer buffer;
    
    Consumer(SharedBuffer b) { buffer = b; }
    
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                buffer.consume();
                Thread.sleep(150); // sleep() для имитации работы
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Ключевые моменты в этом примере:

  • wait() вызывается в цикле while (а не в if), чтобы избежать ложного пробуждения (spurious wakeup)
  • notify() будит только один поток. Если нужно разбудить все — используйте notifyAll()
  • sleep() используется только для симуляции времени работы, а не для синхронизации
  • Все методы produce() и consume() синхронизированы, и wait() вызывается внутри них

Когда что использовать?

  • sleep() — когда нужно просто подождать фиксированное время (таймаут, задержка между операциями)
  • wait() + notify() — когда потоки должны координировать свою работу: один ждет, пока другой выполнит определенное условие

Запомните главное правило: wait() отдает блокировку и ждет сигнала, sleep() держит блокировку и просто спит.

<<>>

Дополнительные нюансы: прерывание и таймауты

Оба метода могут выбросить InterruptedException, но реагируют на прерывание по-разному:

  • sleep() — при прерывании поток просыпается немедленно и выбрасывает исключение. Блокировки, которые он держал, остаются захваченными
  • wait() — при прерывании поток также просыпается, но ему нужно повторно захватить монитор (блокировку) перед тем, как выбросить исключение

Варианты с таймаутом

У wait() есть перегруженная версия wait(long timeout), которая работает как комбинация sleep() и wait():

synchronized (lock) {
    // Ждем не более 1 секунды или пока не вызовут notify()
    lock.wait(1000); 
    // Проснулись либо по таймауту, либо по notify()
}

В отличие от sleep(), wait(timeout) освобождает блокировку на время ожидания. Если вы хотите просто подождать с освобождением блокировки — используйте wait(timeout), если без освобождения — sleep().

Практический совет

Никогда не используйте sleep() для синхронизации потоков — это приведет к race conditions и трудноуловимым багам. Для координации потоков всегда используйте wait()/notify() или высокоуровневые конструкции из java.util.concurrent (например, BlockingQueue).