В чем разница между wait() и sleep() в Java? Когда что использовать?
Всем привет! Я только начал разбираться с многопоточностью в Java и запутался в двух методах: wait() и sleep(). Оба как бы "приостанавливают" поток, но ведут себя по-разному, и я не понимаю, когда что применять.
Вот пример, который я написал. Я хочу, чтобы один поток выводил числа, а второй — буквы, чередуясь. Но у меня получается каша:
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(i);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
for (char c = 'A'; c < 'F'; c++) {
System.out.println(c);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
t1.start();
t2.start();
}
}
Они просто выводят всё вперемешку. Я слышал, что wait() и notify() нужны для синхронизации, но не понимаю, как их прикрутить. В чём принципиальная разница между wait() и sleep()? И почему sleep() не решает мою задачу?
Главное отличие: sleep() — пауза, wait() — освобождение монитора
Ключевое различие между sleep() и wait() в том, что sleep() просто приостанавливает текущий поток на заданное время, не отпуская блокировки (монитора), которые он удерживает. wait() же освобождает монитор и переводит поток в состояние ожидания, пока другой поток не вызовет notify() или notifyAll() на том же объекте.
Детальное сравнение
| Характеристика | sleep() | wait() |
|---|---|---|
| Принадлежит | Классу Thread (статический) | Классу Object (нестатический) |
| Освобождает монитор? | Нет | Да |
| Требует синхронизации? | Нет | Да (должен быть внутри synchronized блока) |
| Пробуждение | Автоматически по таймеру | По notify()/notifyAll() или по таймеру (если есть аргумент) |
| Назначение | Пауза в выполнении | Межпоточная координация |
Как исправить ваш пример с помощью wait/notify
Для чередования потоков нужно использовать общий объект-монитор и флаг, указывающий, чья очередь:
public class AlternatingExample {
private static final Object lock = new Object();
private static boolean isNumberTurn = true;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
while (!isNumberTurn) { // ждём, пока не наша очередь
try { lock.wait(); } catch (InterruptedException e) {}
}
System.out.println(i);
isNumberTurn = false;
lock.notify(); // будим другой поток
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
for (char c = 'A'; c < 'F'; c++) {
while (isNumberTurn) { // ждём, пока не наша очередь
try { lock.wait(); } catch (InterruptedException e) {}
}
System.out.println(c);
isNumberTurn = true;
lock.notify(); // будим другой поток
}
}
});
t1.start();
t2.start();
}
}
Вывод будет строго: 0, A, 1, B, 2, C, 3, D, 4, E.
Когда что использовать?
- sleep() — когда нужно просто подождать (например, задержка между повторными попытками соединения, анимация). Не требует синхронизации.
- wait()/notify() — когда потоки должны координировать действия (производитель-потребитель, чередование, ожидание ресурса). Всегда в паре с synchronized.
Важные детали
sleep()можно вызвать из любого места,wait()— только внутриsynchronizedблока/метода.- После
wait()поток просыпается и снова пытается захватить монитор — поэтому всегда используйтеwhileдля проверки условия (ложное пробуждение). sleep()не сбрасывает состояние гонки — если поток уснул, удерживая блокировку, другие потоки не смогут её получить.
Дополнение: практические сценарии и подводные камни
Коллега выше отлично объяснил теорию. Добавлю несколько практических моментов, которые часто сбивают новичков.
1. sleep() в synchronized блоке — это deadlock-ловушка
Если вы вызываете sleep() внутри synchronized блока, поток засыпает, не отпуская блокировку. Другие потоки, которым нужен этот монитор, будут бесконечно ждать. wait() таких проблем не создаёт.
// ПЛОХО: поток спит, блокируя других
synchronized(lock) {
Thread.sleep(5000); // lock занят всё это время!
}
2. wait() можно вызвать с таймаутом
wait(1000) — поток проснётся либо по notify(), либо через 1 секунду. Это удобно, когда вы ждёте условие, но не хотите ждать вечно:
synchronized(lock) {
while (!condition) {
lock.wait(2000); // ждём максимум 2 секунды
if (!condition) break; // таймаут — выходим
}
}
3. Всегда проверяйте условие в цикле, а не в if
Поток может проснуться без notify() (spurious wakeup). Поэтому:
// НЕПРАВИЛЬНО:
if (!condition) lock.wait();
// ПРАВИЛЬНО:
while (!condition) lock.wait();
4. notify() vs notifyAll()
notify() будит один случайный поток, notifyAll() — все. Если у вас несколько потоков ждут разных условий, используйте notifyAll(), иначе можно получить deadlock.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Тестирование в JavaScript: Unit, Integration и E2E тесты с Node.js
Топ-10 ошибок начинающих Java разработчиков: как их избежать