Object.wait(): примеры (JAVA)
Object.wait(): voidОбщее описание Object.wait()
Метод Object.wait() - базовый механизм ожидания в Java, встроенный в класс java.lang.Object. Он переводит текущий поток в состояние ожидания до тех пор, пока другой поток не пробудит его с помощью notify() или notifyAll(), либо пока не истечет заданный таймаут, либо пока поток не будет прерван. Метод освобождает захваченный монитор объекта на время ожидания и повторно захватывает его перед возвратом.
Сигнатуры:
public final void wait() throws InterruptedExceptionpublic final void wait(long timeout) throws InterruptedExceptionpublic final void wait(long timeout, int nanos) throws InterruptedException
Параметры и поведение:
- wait(): ожидает неограниченно до notify/notifyAll/interrupt.
- wait(long timeout): ожидает не более timeout миллисекунд. Если timeout < 0, выбрасывается IllegalArgumentException. timeout == 0 интерпретируется как ожидание без таймаута.
- wait(long timeout, int nanos): ожидает не более timeout миллисекунд + nanos наносекунд. nanos должен быть в диапазоне 0..999999, иначе IllegalArgumentException. Если timeout == 0 и nanos == 0 - аналогично бесконечному ожиданию.
Возвращаемое значение: метод возвращает void. Причины выхода из ожидания:
- получено уведомление через notify() или notifyAll();
- текущее ожидание прервано - InterruptedException;
- истек таймаут (для версий с параметрами);
- возможны спонтанные пробуждения (spurious wakeups), поэтому рекомендуется проверять условие в цикле.
Исключения и требования:
- IllegalMonitorStateException - если метод вызван без владения монитором объекта (не в synchronized блоке/методе).
- InterruptedException - если поток прерван во время ожидания.
- IllegalArgumentException - при некорректных значениях timeout или nanos (см. выше).
Рекомендация: использовать wait внутри цикла, проверяющего условие, чтобы учесть spurious wakeups и возможные пропуски уведомлений.
Короткие примеры использования
1) Простейший пример wait/notify.
public class SimpleWaitNotify {
private final Object lock = new Object();
public void runExample() throws InterruptedException {
Thread waiter = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("waiter: перед wait");
lock.wait();
System.out.println("waiter: после wait");
} catch (InterruptedException e) {
System.out.println("waiter: прерван");
}
}
});
Thread notifier = new Thread(() -> {
synchronized (lock) {
System.out.println("notifier: перед notify");
lock.notify();
System.out.println("notifier: после notify");
}
});
waiter.start();
Thread.sleep(100); // дать время waiter зайти в wait
notifier.start();
waiter.join();
notifier.join();
}
public static void main(String[] args) throws Exception {
new SimpleWaitNotify().runExample();
}
}
waiter: перед wait notifier: перед notify notifier: после notify waiter: после wait
2) Timeout-вариант.
synchronized (lock) {
System.out.println("до wait с таймаутом 500 мс");
lock.wait(500);
System.out.println("после wait (либо notify, либо таймаут)");
}
до wait с таймаутом 500 мс после wait (либо notify, либо таймаут)
3) Пример с nanos (короткий демонстративный):
synchronized (lock) {
lock.wait(1, 500_000); // 1 мс + 500_000 нс = 1.5 мс
}
(поведение аналогично ожиданию с очень маленьким таймаутом)
4) Ошибка вызова без синхронизации.
Object o = new Object();
o.wait(); // приведет к исключению
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.wait(Native Method)
...Похожие механизмы в Java
В Java имеются более высокоуровневые и гибкие средства синхронизации:
- java.util.concurrent.locks.Condition - применяется вместе с ReentrantLock. Позволяет ждать с той же семантикой, но предоставляет отдельные очереди ожидания и явное управление блокировкой. Предпочтителен при сложной логике и при использовании ReentrantLock.
- LockSupport.park()/unpark() - низкоуровневый блокирующий механизм для построения своих примитивов блокировки; не требует владения монитором и работает на уровне потоков.
- Высокоуровневые структуры (Semaphore, CountDownLatch, CyclicBarrier, BlockingQueue) - заменяют явное wait/notify в большинстве типовых сценариев (производитель-потребитель, ожидание завершения и т.п.).
Когда использовать что:
- Object.wait()/notify() - подойдет для простых случаев и при необходимости совместимости с существующим кодом, но требует аккуратного обращения с монитором.
- Condition - при необходимости гибкой, выразительной и безопасной работы с блокировками.
- BlockingQueue и другие утилиты - когда требуется готовая потокобезопасная структура без риска ошибок с notify/notifyAll.
Аналоги в других языках и заметки
Ниже краткие соответствия и отличия с примерами.
Python (threading.Condition)
import threading
cond = threading.Condition()
def waiter():
with cond:
print('py: до wait')
cond.wait()
print('py: после wait')
threading.Thread(target=waiter).start()
# в другом потоке: with cond: cond.notify()
py: до wait py: после wait
C# (Monitor.Wait / Monitor.Pulse)
using System;
using System.Threading;
object o = new object();
new Thread(() => {
lock (o) {
Console.WriteLine("cs: до Wait");
Monitor.Wait(o);
Console.WriteLine("cs: после Wait");
}
}).Start();
// в другом потоке: lock(o){ Monitor.Pulse(o); }
cs: до Wait cs: после Wait
Go (sync.Cond)
var mu sync.Mutex
cond := sync.NewCond(&mu)
go func() {
mu.Lock()
fmt.Println("go: до Wait")
cond.Wait()
fmt.Println("go: после Wait")
mu.Unlock()
}()
// в другом горутине: cond.Signal()
go: до Wait go: после Wait
JavaScript (Atomics.wait на SharedArrayBuffer в среде, где поддерживается)
// только в средах, где доступен Atomics.wait (Worker + SharedArrayBuffer)
// Atomics.wait(typedArray, index, value, timeout)
возвращает 'ok' или 'timed-out' или 'not-equal'
Lua (корутины)
co = coroutine.create(function()
print('lua: до yield')
coroutine.yield()
print('lua: после resume')
end)
coroutine.resume(co)
coroutine.resume(co)
lua: до yield lua: после resume
PHP: в стандартной среде отсутствует прямой эквивалент; в pthreads или при использовании внешних процессов применяются другие подходы (семафоры, очереди, блокировки в расширениях).
Отличия от Java:
- Во многих языках ожидание привязано не к любому объекту, а к специализированным структурам (Condition, Event, Channel).
- Java требует владения монитором у вызывающего; в других средах это требование может отсутствовать.
- В Java часто используются высокоуровневые структуры вместо прямого использования wait/notify.
Типичные ошибки при использовании
1) Вызов wait без владения монитором - IllegalMonitorStateException.
public class BadWait {
public static void main(String[] args) throws Exception {
Object o = new Object();
o.wait(); // ошибка
}
}
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.wait(Native Method)
...
2) Игнорирование InterruptedException - потеря информации о прерывании. Рекомендуется корректно обрабатывать прерывания или повторно прерывать поток.
3) Использование if вместо while при ожидании условия - слабая защита от spurious wakeup и логики с несколькими уведомлениями.
// Неправильно
synchronized(lock) {
if (!ready) {
lock.wait();
}
// предполагается ready == true, но может быть false
}
// Правильно
synchronized(lock) {
while (!ready) {
lock.wait();
}
}
if: возможна ошибка при спонтанном пробуждении while: условие перепроверяется, безопаснее
4) Использование notify() вместо notifyAll() в ситуациях с несколькими типами ожиданий - может привести к взаимной блокировке (deadlock), если пробуждается не тот поток.
5) Некорректные значения таймаута (отрицательные) при wait(long) / wait(long, int) - IllegalArgumentException.
Изменения и современные практики
Сам метод Object.wait() не претерпел значительных изменений в последних версиях Java и остается частью базового API без депрекации. Однако появились и активнее используются альтернативы:
- java.util.concurrent и Lock/Condition для более явного управления блокировками;
- LockSupport и структуры из java.util.concurrent для построения масштабируемых примитивов;
- Проекты виртуальных потоков (Project Loom) меняют подход к конкурентности, делая модель потоков дешевле, что влияет на выбор инструментов, но не меняет поведение wait/notify.
Резюме: Object.wait() сохраняет прежнюю специфику; при новой разработке рекомендуется рассматривать высокоуровневые структуры или Lock/Condition в зависимости от задач.
Расширенные и редкие сценарии применения
1) Буфер с фиксированным размером (producer-consumer) с использованием wait/notifyAll.
import java.util.LinkedList;
public class BoundedBuffer {
private final LinkedList q = new LinkedList<>();
private final int capacity;
public BoundedBuffer(int capacity) {
this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
synchronized (this) {
while (q.size() == capacity) {
wait();
}
q.addLast(item);
notifyAll(); // уведомить потребителей
}
}
public T take() throws InterruptedException {
synchronized (this) {
while (q.isEmpty()) {
wait();
}
T item = q.removeFirst();
notifyAll(); // уведомить производителей
return item;
}
}
}
// Результат: корректная работа нескольких производителей и потребителей без busy-wait
(при запуске с несколькими потоками данные корректно передаются между потоками без утечек и проигнорированных уведомлений)
2) Реализация ожидания с таймаутом и повторными попытками (retry/backoff).
synchronized (monitor) {
long timeout = 5000; // мс
long deadline = System.currentTimeMillis() + timeout;
while (!conditionMet()) {
long remaining = deadline - System.currentTimeMillis();
if (remaining <= 0) {
// таймаут
break;
}
monitor.wait(remaining);
}
}
(если условие не выполнено до истечения deadline - выполняется ветка таймаута)
3) Комбинация wait/notify с несколькими очередями ожидания (эмуляция Condition до появления Lock).
// разделение логики уведомлений по типам событий
class MultiWait {
private final Object lock = new Object();
private boolean alphaReady = false;
private boolean betaReady = false;
void waitAlpha() throws InterruptedException {
synchronized (lock) {
while (!alphaReady) lock.wait();
alphaReady = false;
}
}
void signalAlpha() {
synchronized (lock) {
alphaReady = true;
lock.notifyAll(); // пробуждаем всех, но код проверит alphaReady
}
}
}
(подход работает, но сложнее и менее эффективен по сравнению с Condition)
4) Использование wait как базовой primitive при реализации собственных синхронизаторов (например, простого Semaphore).
class SimpleSemaphore {
private int permits;
public SimpleSemaphore(int permits) { this.permits = permits; }
public synchronized void acquire() throws InterruptedException {
while (permits == 0) wait();
permits--;
}
public synchronized void release() {
permits++;
notify();
}
}
(работает, но для продакшн-качества лучше использовать java.util.concurrent.Semaphore)
5) Обработка прерываний: аккуратная схема повторной установки флага прерывания.
synchronized (lock) {
boolean interrupted = false;
try {
while (!condition) {
try {
lock.wait();
} catch (InterruptedException e) {
interrupted = true; // не терять информацию
}
}
} finally {
if (interrupted) Thread.currentThread().interrupt();
}
}
(прерывание обработано, но сохранен флаг прерывания для верхних уровней)
Пояснения: в расширенных сценариях wait/notify требует строгого соблюдения правил владения монитором и проверки условия в цикле. Для сложных задач предпочтительнее использовать Lock/Condition или готовые синхронизаторы.