В Java-программе с использованием wait()/notify() опубликован следующий паттерн: if (queue.isEmpty()) queue.wait(); /* затем обработка */ — опишите проблему с потерянным сигналом и гонкой, и предложите безопасный шаблон использования ожидания с условием и синхронизацией?
Проблема (коротко): - Потерянный сигнал: если проверка условия и вызов `wait()` не выполняются атомарно относительно `notify()` (например проверка вне синхронизации или используется `if` + `notify()` от другого потока), то поток может пропустить уведомление — уведомление произойдёт до того, как поток начнёт ждать, и тогда поток повиснет навсегда. - Гонка / неверная реакция после пробуждения: даже если `wait()` вызван корректно под монитором, спорадические пробуждения и конкуренция нескольких потребителей означают, что после пробуждения условие могло уже не выполняться. Использование `if` вместо `while` приведёт к тому, что пробуждённый поток продолжит обработку при уже ложном условии. Безопасный шаблон (синхронизация + цикл проверки): - Используйте общий монитор (обычно тот же объект `queue`) и проверяйте условие в цикле `while`. - Производитель должен уведомлять (`notifyAll()` предпочтительнее, если несколько ожидающих потоков). Пример с монитором `queue`: synchronized (queue) { while (queue.isEmpty()) { queue.wait(); } Object item = queue.remove(); } // обработка item вне синхронизованного блока при желании Производитель: synchronized (queue) { queue.add(item); queue.notifyAll(); // notifyAll предпочтительнее при множественных слушателях } Альтернатива с Lock/Condition: final Lock lock = new ReentrantLock(); final Condition notEmpty = lock.newCondition(); lock.lock(); try { while (queue.isEmpty()) { notEmpty.await(); } Object item = queue.remove(); } finally { lock.unlock(); } Производитель: lock.lock(); try { queue.add(item); notEmpty.signalAll(); } finally { lock.unlock(); } Рекомендация: по возможности используйте готовые классы из java.util.concurrent (например, BlockingQueue), они реализуют безопасную проверку/ожидание и избавляют от этих ошибок.
- Потерянный сигнал: если проверка условия и вызов `wait()` не выполняются атомарно относительно `notify()` (например проверка вне синхронизации или используется `if` + `notify()` от другого потока), то поток может пропустить уведомление — уведомление произойдёт до того, как поток начнёт ждать, и тогда поток повиснет навсегда.
- Гонка / неверная реакция после пробуждения: даже если `wait()` вызван корректно под монитором, спорадические пробуждения и конкуренция нескольких потребителей означают, что после пробуждения условие могло уже не выполняться. Использование `if` вместо `while` приведёт к тому, что пробуждённый поток продолжит обработку при уже ложном условии.
Безопасный шаблон (синхронизация + цикл проверки):
- Используйте общий монитор (обычно тот же объект `queue`) и проверяйте условие в цикле `while`.
- Производитель должен уведомлять (`notifyAll()` предпочтительнее, если несколько ожидающих потоков).
Пример с монитором `queue`:
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
Object item = queue.remove();
}
// обработка item вне синхронизованного блока при желании
Производитель:
synchronized (queue) {
queue.add(item);
queue.notifyAll(); // notifyAll предпочтительнее при множественных слушателях
}
Альтернатива с Lock/Condition:
final Lock lock = new ReentrantLock();
final Condition notEmpty = lock.newCondition();
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
Object item = queue.remove();
} finally {
lock.unlock();
}
Производитель:
lock.lock();
try {
queue.add(item);
notEmpty.signalAll();
} finally {
lock.unlock();
}
Рекомендация: по возможности используйте готовые классы из java.util.concurrent (например, BlockingQueue), они реализуют безопасную проверку/ожидание и избавляют от этих ошибок.