Захватывающий вопрос, это не супер тривиально, но я постараюсь объяснить это как можно более кратко.
Хотя многопоточность не гарантирует какого-либо порядка выполнения, для этого ответа давайте представим, что сначала происходят две последовательности:
- Все потоки запускаются в одно и то же время
- Все потоки одновременно вызывают
barrier.await()
.
В этом случае вы увидите вывод типа
Try to acquire barrier for 0
Try to acquire barrier for 1
Try to acquire barrier for 2
Try to acquire barrier for 3
Try to acquire barrier for 4
Try to acquire barrier for 5
Текущее состояние ваших 6 потоковследующие:
Потоки 0
и 1
await
на общем Condition
, поскольку три потока еще не достигли барьера "пока"
Нить 2
по-прежнему работает в качестве нити "отключения" для барьера
Потоки 3
, 4
и 5
будут ожидать наlock.lock
вызов барьера (тот же экземпляр Lock
, в котором был создан Condition
.
Когда барьер видит нить 2
, он ранее записал 0
и1
как достигнув барьера, так что он знает, что этот цикл завершен и выпустит 0
и 1
. Но перед тем, как освободить два других потока, ему нужно запустить barrierAction
, который вы определили как спящий, на 5 секунд, и он это сделает.
Затем вы увидите вывод
Barrier action starting
Barrier action finishing
Поток 2
все еще удерживает блокировку, равен RUNNABLE
и готов к выходу из барьера, так что он делает это, и вы видите этоoutput.
barrier acquired for 2
Но до того, как существует поток 2
, он будет сигнализировать всем другим потокам, ожидающим текущий барьер. Вот где это становится сложнее, await
на 0
и 1
делается на общем Condition
. Condition
является общим для всех барьерных «поколений». Таким образом, несмотря на то, что первые два потока await
предшествовали вторым 3 потокам lock
, когда signalAll
завершено, потоки первого поколения все еще должны ждать своей очереди на пробуждение.
На данный момент у нас есть 5 потоков в состоянии BLOCKED
(3, 4 и 5) или TIMED_WAITING
(0, 1). В этом примере важно время, когда они блокируют / ждут на Lock
. Если все они происходят в том порядке, в котором очередь для критической секции была бы:
Thread-0 -> Thread-1 -> Thread-5 -> Thread-4 -> Thread-3
| |
TAIL HEAD
, так что следующие освобожденные потоки будут Thread-3
, затем 4
, а затем 5
. Причина, по которой эта очередь выглядит так, заключается в том, что все потоки достигают lock
одновременно, и все они в очереди, потоки 0
и 1
, очевидно, сначала достигают его и, таким образом, попадают в критическую секцию барьера, но затемawait
в то время как поток 2
входит, чтобы разбудить их, но теперь 0
и 1
помещаются в конец очереди, и 3, 4 и 5 будут вызваны следующими.
Когдапоток 2
покидает барьер, и signalAll
потоки 3
и 4
будут работать, и, поскольку они являются частью второго поколения, будут приостановлены, пока поток 5
не пройдет и не запустит действие barrier
. Затем он печатает
Barrier action starting
Barrier action finishing
Наконец, поток 5
снова будет signalAll
, а остальные потоки завершатся.
В этом случае вы увидите поток 5
Первый завершен, а остальные следуют
barrier acquired for 5
barrier acquired for 0
barrier acquired for 1
barrier acquired for 3
barrier acquired for 4