Почему java.util.concurrent.ArrayBlockingQueue использует циклы while вместо 'if' для вызовов await ()?

Для большого объема данных System.out.println может быть неэффективным, так как он не очень хорошо буферизует. В этом случае вы можете использовать BufferedOutputStream или BufferedWriter.

13
задан Gray 11 October 2011 в 15:12
поделиться

4 ответа

Для защиты от ложного пробуждения. JVM не дает вам никаких гарантий, что единственная возможная причина, по которой поток когда-либо снова начнет работать, - это то, что вы вызвали сигнал так, как вы планировали. Иногда он просто запускается случайно и уходит (ложное пробуждение). Поэтому вам придется снова ждать, если условие, по которому вы хотите работать, на самом деле не выполняется.

Это объясняется в javadoc для метода ожидания: http://java.sun.com/javase/6/docs/api/java/lang/Object.html#wait%28long%29

И упоминается в документации для await: http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/Condition.html#await%28%29

Блокировка, связанная с этим Состояние освобождается атомарно и текущий поток становится отключенным для планирования потоков и бездействует, пока одна из четырех вещей происходит:

  • Другой поток вызывает метод signal () для этого условия и текущий поток оказывается выбран как нить для пробуждения; или

  • Какой-то другой поток вызывает метод signalAll () для этого Condition; или

  • Другой поток прерывает текущий поток, и прерывание поддерживается резьбовая подвеска; или

* Произошло "ложное пробуждение".

Некоторые реализации интерфейса Condition могут подавлять ложные пробуждения, но полагаться на это, следовательно, будут полагаться на детали реализации и сделать ваш код непереносимым.

20
ответ дан 1 December 2019 в 19:14
поделиться

Почему java.util.concurrent.ArrayBlockingQueue использует циклы while вместо if вокруг вызовов await ()?

Они используют while вместо if для защиты от классических условий гонки потоков в моделях производитель / потребитель и для защиты от гораздо более редких случаев ложного пробуждения.

Когда (например) несколько потребителей ожидают определенного условия (например, очередь пуста), и условие получает уведомление, есть вероятность, что другой поток может сначала заблокировать и «украсть» элемент, добавленный в очередь. Цикл while необходим для потока, чтобы убедиться, что в очереди есть элемент, прежде чем он попытается вывести его из очереди.

Пример кода

Я написал несколько примеров кода и дополнительную документацию , которая демонстрирует состояние гонки.

Описание состояния гонки

Если посмотреть на ваш конкретный код, гонка выглядит следующим образом:

  1. поток № 1, потребитель, находится в await () в цикле while, ожидая для того, чтобы в очереди
  2. были элементы, поток №2, производитель, блокирует очередь
  3. поток №3, потребитель, завершает потребление последнего элемента, вызывает get () , блокирует в очереди и должен ждать разблокировки №2 (это НЕ , ожидающий hasItems , но ожидающий получения блокировки )
  4. поток №2 добавляет элемент в очередь и вызывает hasItems.signal () , чтобы уведомить кого-то о том, что там есть элемент
  5. поток №1 просыпается и переходит к блокировке очереди, должен ждать , пока №2 разблокирует
  6. поток №2 unlocks
  7. поток №3 на впереди потока №1, ожидающего блокировки, поэтому он сначала блокирует очередь, переходит в цикл while и удаляет из очереди элемент, о котором было сообщено №1, а затем разблокирует
  8. поток №1 теперь может блокироваться. Если бы это был просто оператор if , он пошел бы вперед и попытался бы выйти из очереди из пустого списка, что вызвало бы исключение ArrayIndexOutOfBoundsException или что-то в этом роде.

Причина, по которой требуется оператор while , заключается в обработке этих условий гонки. На шаге 8 выше, с while , поток №1 вместо этого вернется к тесту, чтобы увидеть, есть ли элементы в очереди, обнаружит, что их нет, и затем вернется в режим ожидания.

Это классическая проблема, которая сбивает с толку многих повторно въезжающих программистов. В начальных версиях Библии О'Рейли pthreads, например, был пример кода без цикла while, и их пришлось переиздать.

В некоторых потоковых системах системе легче пробудить все условия, а не конкретное условие, о котором было сообщено, так что может произойти «ложное пробуждение» . Циклы while также защищают от этого.

14
ответ дан 1 December 2019 в 19:14
поделиться

Возможно, вы не поняли, но в оригинальном коде используется while вместо if, потому что может быть несколько потоков, слушающих/потребляющих очередь...

0
ответ дан 1 December 2019 в 19:14
поделиться

Другая проблема с вашей реализацией - это потенциально потерянный "сигнал".Если вы внимательно посмотрите на jdk impls, которые обрабатывают InterruptedException, некоторые из них сигнализируют перед возвратом. это потому, что поток может быть выбран для передачи сигналов, а затем будет прерван, и в этом случае этот сигнал будет потерян. таким образом, при прерывании jdk подразумевает повторный сигнал перед выходом из строя. поскольку этот поток мог или не мог фактически получать сигнал, это также может вызвать «ложное пробуждение» (как упоминалось ранее).

3
ответ дан 1 December 2019 в 19:14
поделиться