wait() vs sleep() в Java: битва титанов многопоточности
Представьте, что вы — дирижёр оркестра. У вас есть скрипачи (потоки), которые должны играть слаженно, но иногда им нужно подождать, пока другие закончат партию. В Java для управления такими паузами есть два мощных инструмента: wait() и sleep(). На первый взгляд они похожи — оба заставляют поток «замереть» на время. Но на деле это совершенно разные механизмы, и путать их — верный способ получить deadlock или race condition.
Эта статья — ваш путеводитель по джунглям многопоточности. Мы не просто перечислим различия, а разберём их на живых примерах, чтобы вы навсегда запомнили: sleep — это пауза, а wait — это освобождение ресурса. Поехали!
Что такое sleep()? (Thread.sleep)
Метод Thread.sleep(миллисекунды) — это самый простой способ «усыпить» текущий поток на заданное время. Поток просто отдыхает, не снимая блокировок с объектов. Он как ленивый кот, который дремлет на диване, но продолжает держать лапой игрушку.
Важные факты о sleep():
- Это статический метод класса
Thread. - Поток не освобождает мониторы (блокировки), которые он удерживает.
- После истечения таймера поток переходит в состояние Runnable (готов к выполнению).
- Может выбросить
InterruptedException.
Простой пример:
public class SleepExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("Начинаем: " + System.currentTimeMillis());
Thread.sleep(2000); // спим 2 секунды
System.out.println("Проснулись: " + System.currentTimeMillis());
}
}
Результат: между двумя выводами будет ровно 2 секунды. Поток просто ждал, ничего не делая.
Что такое wait()? (Object.wait)
Метод wait() — это уже не просто пауза, а механизм межпоточной коммуникации. Он определён в классе Object, и вызывать его можно только внутри синхронизированного блока (synchronized). Когда поток вызывает wait(), он:
- Освобождает монитор объекта (то есть отпускает блокировку).
- Переходит в состояние WAITING или TIMED_WAITING (если указан таймаут).
- Ждёт, пока другой поток не вызовет
notify()илиnotifyAll()на том же объекте.
Это как если бы вы сказали: «Я кладу вилку и жду, пока мне принесут второе блюдо». Вы не просто спите — вы освобождаете место за столом для другого.
Базовый пример:
public class WaitNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waiter = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Поток-официант ждёт...");
lock.wait(); // отпускает lock и ждёт
System.out.println("Поток-официант получил уведомление!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread notifier = new Thread(() -> {
synchronized (lock) {
System.out.println("Поток-повар готовит...");
try {
Thread.sleep(1000); // имитация готовки
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
lock.notify(); // будит ожидающий поток
System.out.println("Повар уведомил официанта!");
}
});
waiter.start();
notifier.start();
waiter.join();
notifier.join();
}
}
Вывод: "Поток-официант ждёт..." → "Поток-повар готовит..." → "Повар уведомил официанта!" → "Поток-официант получил уведомление!"
Ключевые различия: таблица сравнения
Давайте сведём всё в наглядную таблицу. Запомните: sleep — для задержки, wait — для синхронизации.
| Критерий | sleep() | wait() |
|---|---|---|
| Класс | Thread | Object |
| Статический? | Да | Нет (вызывается на объекте) |
| Освобождает блокировку? | Нет | Да |
| Может быть вызван вне synchronized? | Да | Нет (вызовет IllegalMonitorStateException) |
| Пробуждение | По таймеру | По таймеру или через notify()/notifyAll() |
| Использование | Пауза, задержка, тайминг | Межпоточное взаимодействие (producer-consumer) |
Когда использовать sleep(), а когда wait()?
Вот несколько сценариев, где каждый из методов будет уместен.
sleep() — идеально для:
- Имитации длительной операции (например, загрузка файла).
- Тайм-аутов в циклах опроса (polling).
- Задержки между повторными попытками (retry logic).
- Тестирования многопоточности в простых сценариях.
wait() — незаменим для:
- Шаблона Producer-Consumer (производитель-потребитель).
- Ожидания определённого условия (например, пока очередь не станет непустой).
- Эффективного использования ресурсов (поток не тратит CPU на пустые проверки).
- Реализации блокирующих очередей (как
BlockingQueueвнутри).
Главные ошибки новичков (и как их избежать)
Ошибка №1: Вызов wait() вне synchronized. Код упадёт с IllegalMonitorStateException. Всегда оборачивайте wait() и notify() в synchronized блок.
Блоги
Рекомендаций по книгам
Читайте также