← Назад к вопросам

Как работает notify?

2.0 Middle🔥 111 комментариев
#UI и вёрстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткий ответ

notify() — это метод класса java.lang.Object, используемый для пробуждения одного (какого именно — зависит от реализации JVM) потока, который находится в состоянии ожидания на мониторе этого же объекта (т.е. вызвал wait()). Он должен вызываться только из синхронизированного контекста (synchronized блока или метода), удерживая монитор того же объекта, на котором был вызван wait().

Детальный механизм работы

Базовые концепции и поток выполнения

  1. Монитор и Взаимоисключающая блокировка (mutex): Каждый объект в Java имеет связанный с ним монитор — внутренний механизм синхронизации. Когда поток входит в synchronized блок (или метод), он захватывает (lock) монитор указанного объекта. Другие потоки, пытающиеся войти в любой synchronized блок по тому же объекту, будут заблокированы.

  2. Состояние WAITING и wait(): Метод wait() переводит текущий поток в состояние ожидания (WAITING или TIMED_WAITING). При этом поток освобождает монитор объекта, что позволяет другим потокам войти в синхронизированные секции по этому объекту. Поток остается в этом состоянии до тех пор, пока не произойдет одно из трех событий: другой поток вызовет notify()/notifyAll() для этого объекта, истечет таймаут (если использовался wait(long timeout)) или поток будет прерван (interrupt()).

  3. Роль notify(): Вызов notify() для объекта пробуждает один случайный поток из множества потоков, ожидающих на мониторе этого объекта (т.е. ранее вызвавших wait() на нем). Важно: поток не пробуждается мгновенно в момент вызова notify(). Пробужденный поток переходит в состояние BLOCKED, потому что он должен заново захватить монитор объекта, который в этот момент, скорее всего, удерживается потоком, вызвавшим notify(). Только после того как монитор будет снова захвачен, пробужденный поток продолжит выполнение с того места, где он остановился после вызова wait().

Типичная последовательность действий (Producer-Consumer)

public class SharedResource {
    private String message;
    private boolean empty = true;

    public synchronized String take() throws InterruptedException {
        while (empty) { // Используем while, а не if, для защиты от спонтанных пробуждений
            wait(); // 1. Поток-потребитель освобождает монитор и ждет
        }
        empty = true;
        notifyAll(); // 4. Пробуждаем продюсера (лучше использовать notifyAll())
        return message;
    }

    public synchronized void put(String newMessage) throws InterruptedException {
        while (!empty) {
            wait(); // 3. Поток-производитель может ждать, если ресурс не пуст
        }
        empty = false;
        this.message = newMessage;
        notify(); // 2. Пробуждаем ОДНОГО ожидающего потребителя
    }
}

Критические замечания и лучшие практики

  • notify() vs notifyAll(): notify() пробуждает только один произвольный поток. notifyAll() пробуждает все ожидающие потоки. В большинстве случаев, особенно в сценариях типа "Producer-Consumer", предпочтительнее использовать notifyAll(). Это предотвращает "зависание" программы, когда сигнал может быть перехвачен не тем потоком (например, другим производителем вместо потребителя). Использование notify() требует тщательного проектирования и часто ведет к трудноотлавливаемым ошибкам.
  • Обязательное условие — цикл while: После пробуждения от wait() поток должен повторно проверить условие, по которому он ожидал, в цикле while, а не в операторе if. Это связано с двумя причинами:
    1.  **Спонтанные пробуждения (spurious wakeups):** В некоторых реализациях/ОС поток может быть пробужден без вызова `notify()`/`notifyAll()`.
    2.  **Проблема "украденного сигнала" (missed signal):** Если два потребителя ждут, а производитель вызовет `notify()`, пробудится один. Второй потребитель останется ждать вечно, если новый сигнал не будет послан.
```java
// ПРАВИЛЬНО
synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    // Выполнять работу, когда condition == true
}

// НЕПРАВИЛЬНО (уязвимо для сбоев)
synchronized(lock) {
    if (!condition) {
        lock.wait();
    }
    // condition может быть ЛОЖНЫМ здесь!
}
```
  • Синхронизация: И wait(), и notify() должны вызываться только из синхронизированного контекста, удерживая монитор объекта. В противном случае будет выброшено исключение IllegalMonitorStateException.

Итог

notify() — это низкоуровневый примитив для кооперативной синхронизации потоков, основанный на мониторах объектов. Он точечно пробуждает один ожидающий поток, но его использование сопряжено с рисками. В современной разработке на Android и Java рекомендуется использовать более высокоуровневые и безопасные механизмы из пакета java.util.concurrent, такие как:

  • BlockingQueue (например, LinkedBlockingQueue) для шаблона Producer-Consumer.
  • CountDownLatch, Semaphore, CyclicBarrier для координации между потоками.
  • ExecutorService и Future для управления пулами потоков и асинхронными задачами.

Эти классы реализуют рассмотренные паттерны внутри себя, избавляя разработчика от необходимости вручную работать с wait()/notify() и минимизируя возможность ошибок.