Object.notify(): примеры (JAVA)
Object.notify(): voidОбщее описание
Метод Object.notify() принадлежит классу java.lang.Object и используется в низкоуровневой модели синхронизации потоков. Метод пробуждает один поток, который ожидает монитора данного объекта (то есть ранее вызвал wait()> на том же объекте). Выбор конкретного потока, если их несколько, выполняется по усмотрению реализации JVM.
Сигнатура метода: public final native void notify(). Метод не принимает аргументов и не возвращает значения (тип void).
Условия вызова и поведение:
- Вызов должен выполняться при владении монитором объекта (внутри блока
synchronized(obj){ ... }или эквивалентного механизма). В противном случае возникаетIllegalMonitorStateException. - Метод помещает в состояние готовности (runnable) ровно один поток из очереди ожидания монитора. Пробужденный поток не сразу продолжит выполнение: перед возобновлением он должен вновь успешно получить монитор объекта.
- Если в очереди ожидания нет потоков, вызов
notify()просто ничего не делает. - Возможны спонтанные пробуждения (spurious wakeups); корректная программа использует цикл с проверкой условия при вызове
wait(). - Часто используется вместе с
wait()и альтернативойnotifyAll().
Исключения и прочие детали:
IllegalMonitorStateException- при попытке вызвать метод без владения монитором.- Метод является
nativeиfinal; реализация зависит от JVM, но спецификация поведения описана в документации Java.
Короткие примеры
Пример 1 - минимальная схема wait/notify для пробуждения одного потока.
class Example1 {
private final Object lock = new Object();
private boolean ready = false;
void waiter() throws InterruptedException {
synchronized (lock) {
while (!ready) {
lock.wait();
}
System.out.println("Поток пробужден, готов = " + ready);
}
}
void notifier() {
synchronized (lock) {
ready = true;
lock.notify();
}
}
public static void main(String[] args) throws Exception {
Example1 ex = new Example1();
Thread t = new Thread(() -> {
try { ex.waiter(); } catch (InterruptedException e) { }
});
t.start();
Thread.sleep(100);
ex.notifier();
}
}
Возможный вывод: Поток пробужден, готов = true
Пример 2 - вызов notify без синхронизации приводит к исключению.
public class Example2 {
public static void main(String[] args) {
Object o = new Object();
o.notify(); // ошибка времени выполнения
}
}
Выполнение приводит к исключению: Exception in thread "main" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method) ... (stack trace)
Пример 3 - различие notify и notifyAll (показано в упрощенном виде).
// Если несколько потоков ждут на одном объекте, notify пробудит один из них.
// notifyAll пробудит всех, и каждый будет пытаться получить монитор по очереди.
Возможный вывод с notify: Поток A продолжил (только один из ожидающих потоков) При использовании notifyAll: Поток A продолжил Поток B продолжил (все ожидающие потоки пробуждаются и конкурируют за монитор)
Похожие механизмы в Java
Список основных альтернатив и их отличия кратко:
- notifyAll() - пробуждает всех потоков в очереди ожидания. Предпочтение зависит от логики: когда несколько условий или сложности с выбором потока, используется notifyAll.
- java.util.concurrent.locks.Condition (в связке с ReentrantLock) - более гибкая замена: поддерживает несколько условных очередей, явную блокировку/разблокировку и явные методы signal() и signalAll().
- Высокоуровневые структуры (BlockingQueue, Semaphore, CountDownLatch, CyclicBarrier) - часто предпочтительнее для типовых задач синхронизации, так как устраняют классы ошибок и упрощают код.
- LockSupport.park/unpark - низкоуровневые примитивы для управления блокировкой потоков; дают больше контроля, но требуют аккуратной работы.
Для большинства сценариев обмена данными между потоками рекомендуются BlockingQueue или Condition вместо прямого использования wait/notify, так как они уменьшают риск ошибок.
Аналоги в других языках
Краткие соответствия и отличия:
- Python:
threading.Conditionс методамиnotify()иnotify_all(). Отличие: Python-реализация позволяет указать число пробуждаемых потоков в notify(n). Пример:
import threading
cond = threading.Condition()
ready = False
def waiter():
with cond:
while not ready:
cond.wait()
print('Пробуждено в Python')
threading.Thread(target=waiter).start()
with cond:
ready = True
cond.notify()
Вывод: Пробуждено в Python
- JavaScript: отсутствие общих нитей в браузере. Для общей памяти в worker-окружении есть
Atomics.waitиAtomics.notifyдля SharedArrayBuffer. Отличие: API работает с числовыми ячейками общей памяти и блокируется только в средах, поддерживающих блокирующие ожидания.
// Пример упрощенный (не выполняется в обычном main-потоке браузера)
// Atomics.wait(typedArray, index, value);
// Atomics.notify(typedArray, index, count);
Результат зависит от среды: один или несколько воркеров пробуждаются.
- C#:
Monitor.Pulse(obj)иMonitor.PulseAll(obj)соответствуют notify/notifyAll. Требуется владение монитором (lock). Также доступны AutoResetEvent/ManualResetEvent и newer Task-based API.
// C#
lock(obj) {
Monitor.Pulse(obj); // пробуждает один
}
Поведение близко к Java: пробуждается один ожидающий поток.
- Go: предпочитаются
channelsдля коммуникации между горутинами. Есть такжеsync.Condс методамиSignal()иBroadcast(), похожими на notify/notifyAll.
// Go с sync.Cond
var mu sync.Mutex
cond := sync.NewCond(&mu)
mu.Lock()
// ... условие
cond.Signal() // пробуждает один
mu.Unlock()
Поведение: один горутин пробуждается и конкурирует за блокировку.
- Kotlin: использует те же примитивы, что JVM:
Any.wait(),Any.notify(). Дополнительно доступны kotlinx.coroutines с каналами и шарт. - PHP: в стандартном PHP нет потоков. В расширениях (pthreads) есть механизмы синхронизации:
Cond::wait(),Cond::signal(). Отличие: требует установленного расширения и специфической модели. - Lua: в чистой Lua нет потоков ОС; при использовании библиотек (Lua Lanes, LuaThreads) предоставляются свои примитивы синхронизации.
- SQL: концепция notify отсутствует; синхронизация на уровне СУБД решается блокировками транзакций, уведомлениями (NOTIFY/LISTEN в PostgreSQL), которые логически отличаются от примитивов потоков в JVM.
Типичные ошибки
Ниже перечислены частые ошибки при работе с notify() и короткие примеры.
- Вызов без владения монитором - приводит к
IllegalMonitorStateException. Пример см. в разделеexamples. - Потерянное уведомление - notify может сработать до того, как поток начал ждать. При таком порядке ожидание может остаться бесконечным. Решение - установить булево условие и проверять его в цикле перед wait:
// Плохо:
// Thread A: notifier() вызывает notify()
// Thread B: позже вызывает wait() и будет ждать вечно
// Правильно: использовать флаг 'ready' и цикл while
Пример проблемы: поток может зависнуть навсегда при некорректном порядке вызовов.
- Использование notify вместо notifyAll - может привести к дедлоку, если пробуждённый поток не тот, который сможет продолжить работу (например, при нескольких типов условий на одном мониторе).
- Неучёт спонтанных пробуждений - важно всегда применять цикл с проверкой условия вокруг wait.
- Игнорирование InterruptedException - при ожидании потоки могут быть прерваны; в обработке прерываний следует корректно реагировать и сохранять семантику прерывания.
Изменения и примечания по версиям
Сам метод Object.notify() присутствует в Java с ранних версий и не претерпевал существенных изменений в сигнатуре или семантике. Основные примечания:
- Метод остаётся
final nativeи документация по его поведению стабильна. - В поздних версиях Java (Project Loom) появились виртуальные потоки; семантика
wait/notifyостается, но стратегию планирования потоков определяет JVM. Для высокоуровневой конкуренции предпочтительнее использовать современные API (CompletableFuture, structured concurrency, каналы корутин и т.д.).
Расширенные и редкие примеры
Пример A - простая блокирующая очередь на wait/notifyAll.
import java.util.LinkedList;
class SimpleBlockingQueue {
private final LinkedList q = new LinkedList<>();
private final int capacity;
SimpleBlockingQueue(int capacity) { this.capacity = capacity; }
public synchronized void put(E item) throws InterruptedException {
while (q.size() == capacity) {
wait();
}
q.addLast(item);
notifyAll();
}
public synchronized E take() throws InterruptedException {
while (q.isEmpty()) {
wait();
}
E item = q.removeFirst();
notifyAll();
return item;
}
}
// Использование: несколько производителей и потребителей.
Вывод (пример): Производитель добавил 1 Потребитель взял 1 ... (взаимодействие между потоками)
Комментарий: используется notifyAll, чтобы избежать проблем выбора неверного потока при нескольких типах ожидающих потоков.
Пример B - работа с таймаутом и обработкой спонтанных пробуждений.
synchronized (lock) {
long deadline = System.currentTimeMillis() + timeoutMillis;
while (!conditionSatisfied()) {
long waitTime = deadline - System.currentTimeMillis();
if (waitTime <= 0) break; // таймаут
lock.wait(waitTime);
}
}
Результат: функция вернётся либо при выполнении условия, либо по таймауту.
Пример C - корректная обработка прерываний при ожидании.
synchronized (lock) {
while (!done) {
try {
lock.wait();
} catch (InterruptedException e) {
// Сохранение статуса прерывания и выход или повторная установка флага
Thread.currentThread().interrupt();
break;
}
}
}
Результат: в случае прерывания поток получает InterruptedException и можно корректно выйти из ожидания.
Пример D - разделение очередей ожидания путём использования разных объектов-мониторов.
// Для разных типов условий применяются разные lock-объекты:
private final Object lockA = new Object();
private final Object lockB = new Object();
// Потоки ждущие A используют lockA.wait(), уведомления для них - lockA.notify()
// Аналогично для B с lockB
Результат: уменьшение конкуренции и выборочного пробуждения нужных потоков.
Пример E - сочетание wait/notify и ReentrantLock/Condition для миграции на более гибкий API.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
Condition cond = lock.newCondition();
lock.lock();
try {
while (!ready) cond.await();
} finally { lock.unlock(); }
// Пробуждение:
lock.lock();
try {
ready = true;
cond.signal();
} finally { lock.unlock(); }
Результат: аналогично wait/notify, но с возможностью иметь несколько Condition и более явным управлением блокировкой.