Почему java.util.concurrent.ArrayBlockingQueue использует циклы while вместо 'if' вокруг вызовов await ()?
Они используют while
вместо if
для защиты от классических условий гонки нитей в моделях производителя / потребителя и для защиты от гораздо более редкого случая ложных пробуждений.
Когда (например) несколько потребителей ожидают определенного условия (например, очередь пуста), и условие получает уведомление, существует вероятность, что другой поток может заблокировать сначала и «украсть» элемент, добавленный в очередь. Цикл while
необходим потоку, чтобы удостовериться, что в очереди есть элемент, прежде чем он попытается снять его с очереди.
Пример кода
Я написал пример кода и дополнительную документацию , которая демонстрирует состояние гонки.
Описание условий гонки
Глядя на ваш конкретный код, гонка выглядит следующим образом:
- Поток # 1, потребитель, находится в
await()
в цикле while, ожидая, что в очереди будут элементы
- поток № 2, производитель, блокировка в очереди
- thread # 3, потребитель, заканчивает потребление последнего элемента, вызывает
get()
, блокирует очередь и должен ждать разблокировки # 2 (это NOT , ожидающее hasItems
но он ждет, чтобы получить lock
)
- thread # 2, добавляет элемент в очередь и вызывает
hasItems.signal()
, чтобы уведомить кого-то, что там есть элемент
- поток # 1 активируется и блокирует очередь, должен ждать , пока # 2 разблокируется
- тема # 2 разблокируется
- поток # 3 на впереди потока # 1 ожидает блокировки, поэтому сначала блокирует очередь, переходит в цикл while и снимает с очереди элемент, о котором был уведомлен № 1, а затем разблокирует
- тема # 1 теперь может блокироваться. Если бы это был просто оператор
if
, он шел бы вперед и пытался удалить из пустого списка, который выкинул бы ArrayIndexOutOfBoundsException
или что-то в этом роде.
Причина, по которой оператор while
необходим, заключается в обработке этих условий гонки. На шаге 8 выше, с while
, поток # 1 вместо этого возвращается к тесту, чтобы увидеть, есть ли элементы в очереди, обнаруживает, что их нет, и затем возвращается к ожиданию.
Это классическая проблема, которая сбивает с толку многих начинающих программистов. Например, первоначальные версии библии O'Reilly pthreads имели пример кода без цикла while и должны были быть переизданы.
В некоторых системах с потоками системе легче пробуждать все условия, а не конкретное условие, о котором было сообщено, так что может произойти «ложное пробуждение» . Петли while
также защищают от этого.