Безопасность и живучесть - это проблемы при использовании механизма ожидания / уведомления. Свойство безопасности требует, чтобы все объекты поддерживали согласованные состояния в многопоточной среде. Свойство liveness требует, чтобы каждая операция или вызов метода выполнялся до конца без прерывания.
Чтобы гарантировать живучесть, программы должны проверить условие цикла while, прежде чем вызывать метод wait (). Этот ранний тест проверяет, выполнил ли другой поток предикат условия и отправил ли уведомление. Вызов метода wait () после отправки уведомления приводит к блокировке на неопределенный срок.
Чтобы гарантировать безопасность, программы должны проверять состояние цикла while после возврата из метода wait (). Хотя wait () предназначена для блокирования на неопределенный срок до получения уведомления, оно все равно должно быть заключено в цикл, чтобы предотвратить следующие уязвимости:
Поток посередине: Третий поток может получить блокировку общего объекта в течение интервала между отправкой уведомления и получением потока, возобновляющего выполнение. Этот третий поток может изменить состояние объекта, оставив его несогласованным. Это состояние гонки с временем проверки (TOCTOU).
Вредоносное уведомление: Случайное или злонамеренное уведомление может быть получено, если предикат условия имеет значение false. Такое уведомление отменит метод wait ().
Уведомление о доставке: Порядок выполнения потоков после получения сигнала notifyAll () не указан. Следовательно, несвязанный поток может начать выполнение и обнаружить, что его предикат условия удовлетворен. Следовательно, он может возобновить выполнение, несмотря на то, что он должен оставаться бездействующим.
Ложные пробуждения: Некоторые реализации виртуальной машины Java (JVM) уязвимы для ложных пробуждений, которые приводят к ожидающим ожиданиям потоков, даже после уведомления.
По этим причинам программы должны проверять предикат условия после возврата метода wait (). Цикл while - лучший выбор для проверки предиката условия как до, так и после вызова wait ().
Точно так же метод await () интерфейса Condition также должен вызываться внутри цикла. Согласно API Java, условие интерфейса
При ожидании условия «ложное пробуждение» разрешено
в целом, как уступка базовой платформе
семантика. Это оказывает небольшое практическое влияние на большинство приложений
программы как условие всегда должны ожидаться в цикле,
тестирование ожидаемого состояния предиката.
реализация бесплатна, чтобы удалить возможность ложных пробуждений
но рекомендуется, чтобы прикладные программисты всегда предполагали, что
они могут происходить и поэтому всегда ждут в цикле.
Новый код должен использовать утилиты параллелизма java.util.concurrent.locks вместо механизма ожидания / уведомления. Однако устаревший код, соответствующий другим требованиям этого правила, может зависеть от механизма ожидания / уведомления.
Пример несовместимого кода
Этот пример несовместимого кода вызывает метод wait () внутри традиционного блока if и не может проверить постусловие после получения уведомления. Если уведомление было случайным или злонамеренным, поток мог проснуться преждевременно.
synchronized (object) {
if (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
Совместимое решение
Это совместимое решение вызывает метод wait () из цикла while для проверки условия как до, так и после вызова wait ():
synchronized (object) {
while (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
Вызовы метода java.util.concurrent.locks.Condition.await () также должны быть заключены в аналогичный цикл.