Добавить в корзинуПозвонить
Найти в Дзене
Записки о Java

Барьерная синхронизация в Java: CountDownLatch, CyclicBarrier и Phaser

В многопоточном программировании часто возникает необходимость синхронизировать выполнение нескольких задач: одна или несколько операций не должны начинаться, пока не завершатся другие. Для этого в Java существует паттерн барьерной синхронизации (Barrier Synchronization), который позволяет потокам «встретиться» в определённой точке и продолжить выполнение только после того, как все достигнут этой точки. В Java в пакете java.util.concurrent для реализации этого паттерна доступны три мощных класса: Давайте разберём каждый из них подробно, с примерами и объяснениями. В паттерне барьерной синхронизации выделяют два типа участников: Это потоки, которые выполняют параллельные задачи и сигнализируют о завершении своей работы. Например, загрузка данных, обработка изображений, вычисления. Это потоки, которые ждут завершения определённого количества задач, прежде чем продолжить своё выполнение. Например, главный поток, который должен собрать результаты всех вычислений. CountDownLatch — это прос
Оглавление
Барьерная синхронизация в Java
Барьерная синхронизация в Java

Введение

В многопоточном программировании часто возникает необходимость синхронизировать выполнение нескольких задач: одна или несколько операций не должны начинаться, пока не завершатся другие. Для этого в Java существует паттерн барьерной синхронизации (Barrier Synchronization), который позволяет потокам «встретиться» в определённой точке и продолжить выполнение только после того, как все достигнут этой точки.

В Java в пакете java.util.concurrent для реализации этого паттерна доступны три мощных класса:

  • CountDownLatch
  • CyclicBarrier
  • Phaser

Давайте разберём каждый из них подробно, с примерами и объяснениями.

Участники барьерной синхронизации

В паттерне барьерной синхронизации выделяют два типа участников:

1️⃣ Исполнители (Workers)

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

2️⃣ Наблюдатели (Observers / Waiters)

Это потоки, которые ждут завершения определённого количества задач, прежде чем продолжить своё выполнение. Например, главный поток, который должен собрать результаты всех вычислений.

1. CountDownLatch — Одноразовый счётчик-барьер

CountDownLatch — это простой и эффективный механизм, который позволяет одному или нескольким потокам ждать, пока другие потоки не завершат выполнение определённого количества операций.

🔧 Как работает:

  • Создаётся с начальным счётчиком: new CountDownLatch(n)
  • Каждый исполнитель вызывает countDown() по завершении своей задачи → счётчик уменьшается на 1.
  • Наблюдатель вызывает await() — блокируется, пока счётчик не достигнет нуля.
  • Одноразовый: после достижения нуля счётчик нельзя сбросить.

📌 Пример: Запуск сервисов перед стартом приложения

Рисунок: пример использования CountDownLatch часть 1
Рисунок: пример использования CountDownLatch часть 1
Рисунок: пример использования CountDownLatch часть 2
Рисунок: пример использования CountDownLatch часть 2

💡 Когда использовать:

  • Инициализация перед стартом основной логики.
  • Ожидание завершения фоновых задач.
  • Тестирование многопоточных сценариев.

2. CyclicBarrier — Многоразовый барьер

CyclicBarrier позволяет группе потоков многократно ждать друг друга в одной точке. В отличие от CountDownLatch, его можно переиспользовать после срабатывания.

🔧 Как работает:

  • Создаётся с количеством участников: new CyclicBarrier(n)
  • Каждый поток вызывает barrier.await() — блокируется, пока все участники не вызовут await().
  • После того как все дойдут до барьера — все потоки одновременно продолжают выполнение.
  • Можно задать действие при срабатывании барьера (Runnable), которое выполнится в одном из потоков перед разблокировкой.

📌 Пример: Расчёт в несколько этапов

Рисунок: использование CyclingBarrier часть 1
Рисунок: использование CyclingBarrier часть 1
Рисунок: использование CyclingBarrier часть 2
Рисунок: использование CyclingBarrier часть 2

💡 Когда использовать:

  • Многопроходные вычисления
  • Пошаговая синхронизация потоков.
  • Игровые циклы, где все игроки должны закончить ход, прежде чем начнётся следующий.

3. Phaser — Гибкий и динамический барьер

Phaser — это самый гибкий и мощный из трёх механизмов. Он объединяет возможности CountDownLatch и CyclicBarrier, но добавляет динамическое управление участниками, фазами и возможностью завершения.

🔧 Как работает:

  • Участники могут регистрироваться и сниматься динамически (register(), arriveAndDeregister()).
  • Поддерживает множество фаз (phases), которые автоматически увеличиваются.
  • Можно проверять текущую фазу, количество зарегистрированных/прибывших участников.
  • Поддерживает иерархию (дерево фазеров) для масштабирования.

📌 Пример: Динамическая обработка задач с возможностью добавления новых

Рисунок: испльзование Phaser часть 1
Рисунок: испльзование Phaser часть 1
Рисунок: использование Phaser часть 2
Рисунок: использование Phaser часть 2
Рисунок: использование Phaser часть 3
Рисунок: использование Phaser часть 3

💡 Когда использовать:

  • Динамически изменяющееся количество задач.
  • Сложные многофазные алгоритмы.
  • Когда нужно гибко управлять жизненным циклом участников.
  • Большие распределённые вычисления с возможностью адаптации.

Советы по выбору

  • CountDownLatch — если вам нужно один раз дождаться завершения фиксированного числа задач.
  • CyclicBarrier — если потоки должны многократно синхронизироваться в одной точке (например, итерации).
  • Phaser — если вам нужна максимальная гибкость, динамическое управление участниками и многофазовые вычисления.

Заключение

Барьерная синхронизация — мощный инструмент для координации потоков в Java. Понимание различий между CountDownLatch, CyclicBarrier и Phaser позволяет писать более эффективный, читаемый и надёжный многопоточный код.

Не используйте synchronized и wait()/notify() там, где можно применить высокоуровневые абстракции из java.util.concurrent. Они не только проще в использовании, но и менее подвержены ошибкам.

Рассмотренные в статье примеры, вы можете найти по адресу:

https://github.com/ShkrylAndrei/blog_yandex/tree/main/src/main/java/info/shkryl/barrierSynchronization