В чем разница между notify() и notifyAll()?
Метод notify() пробуждает один произвольный поток из wait set монитора, а notifyAll() пробуждает все ожидающие потоки. Выбор пробуждаемого потока при notify() не определён спецификацией и зависит от реализации JVM.
Сравнение
| Критерий | notify() |
notifyAll() |
|---|---|---|
| Количество пробуждённых потоков | Один (произвольный) | Все |
| Какой именно поток | Не определено (зависит от JVM) | Все, но монитор захватит только один |
| Риск потери сигнала | Да — может разбудить «не тот» поток | Нет — все потоки проверят условие |
| Производительность | Выше (меньше переключений контекста) | Ниже при большом числе ожидающих |
Проблема потери сигнала с notify()
Если несколько потоков ждут на одном мониторе, но с разными условиями, notify() может пробудить поток, для которого условие всё ещё не выполнено:
Пример потери сигнала
// Поток A ждёт: while (!dataReady) lock.wait();
// Поток B ждёт: while (!spaceAvailable) lock.wait();
// Производитель вызывает notify() после записи данных
// JVM может разбудить поток B, а не A
// Поток B проверит spaceAvailable — оно false — снова wait()
// Сигнал потерян, поток A продолжает спать
Когда безопасно использовать notify()
notify() безопасен только при одновременном выполнении двух условий:
- Все ожидающие потоки ждут одного и того же условия.
- Каждое уведомление актуально максимум для одного потока.
Во всех остальных случаях используйте notifyAll().
Вывод
На практике notifyAll() — более безопасный выбор. Используйте notify() только при уверенности, что условие ожидания одинаково для всех потоков в wait set. Для более гибкого управления используйте Condition из ReentrantLock, который позволяет создавать отдельные очереди ожидания для разных условий.
На собеседовании: объясните проблему потери сигнала (lost notification) и когда
notify()безопасен. Это показывает глубокое понимание механизма.