Gymterview
middle

В чем заключаются различия между CyclicBarrier и CountDownLatch?

Оба класса из пакета java.util.concurrent являются синхронизаторами, позволяющими координировать работу нескольких потоков. Однако они различаются по модели использования.

CountDownLatch (замок с обратным отсчётом)

CountDownLatch позволяет одному или нескольким потокам ждать, пока набор операций в других потоках не будет завершён. Счётчик уменьшается вызовом countDown() и не может быть сброшен – одноразовый.

Пример
CountDownLatch latch = new CountDownLatch(3); // Ждём 3 события

// Рабочие потоки:
latch.countDown(); // Уменьшает счётчик на 1

// Ожидающий поток:
latch.await(); // Блокируется, пока счётчик не станет 0

CyclicBarrier (циклический барьер)

CyclicBarrier – точка синхронизации, где указанное количество потоков встречается и одновременно продолжает выполнение. Барьер многоразовый – после «слома» он автоматически сбрасывается.

Пример
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("Все 3 потока на месте, продолжаем!");
});

// Каждый из 3 потоков:
barrier.await(); // Блокируется, пока все 3 не вызовут await()

Сравнение

Характеристика CountDownLatch CyclicBarrier
Повторное использование Нет (одноразовый) Да (сбрасывается автоматически)
Кто уменьшает счётчик Любой поток вызовом countDown() Сам поток вызовом await()
Кто ожидает Потоки, вызвавшие await() Все потоки-участники (взаимное ожидание)
Один поток может сделать countDown() несколько раз Да Нет (один поток – один await())
Опциональное действие при завершении Нет Да (barrierAction в конструкторе)
Модель «Ожидание завершения N событий» «Встреча N потоков в точке»
Обработка ошибок Нет специальной BrokenBarrierException, если один из потоков прерван или вышел по таймауту

Примеры применения

Пример: CountDownLatch -- ожидание готовности сервисов
public class ServiceStartup {
    public static void main(String[] args) throws InterruptedException {
        int serviceCount = 3;
        CountDownLatch latch = new CountDownLatch(serviceCount);

        // Запуск сервисов
        for (String name : List.of("БД", "Кэш", "Очередь")) {
            new Thread(() -> {
                System.out.println("Запуск сервиса: " + name);
                try { Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 3000)); }
                catch (InterruptedException e) { return; }
                System.out.println(name + " готов");
                latch.countDown();
            }).start();
        }

        // Основной поток ждёт, пока все сервисы не стартуют
        latch.await();
        System.out.println("Все сервисы запущены, приложение готово!");
    }
}
Пример: CyclicBarrier -- параллельная обработка с фазами
public class ParallelPhases {
    public static void main(String[] args) {
        int workers = 3;
        CyclicBarrier barrier = new CyclicBarrier(workers, () ->
            System.out.println("--- Все потоки завершили фазу ---"));

        for (int i = 0; i < workers; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    for (int phase = 1; phase <= 3; phase++) {
                        System.out.println("Поток " + id + ": фаза " + phase);
                        Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1500));
                        barrier.await(); // Ждём остальных
                        // Барьер автоматически сбрасывается для следующей фазы
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

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

На собеседовании. Три ключевых отличия: (1) CountDownLatch одноразовый, CyclicBarrier многоразовый; (2) CountDownLatch разделяет «отмечающие» и «ожидающие» потоки, CyclicBarrier – все потоки равноправны; (3) CyclicBarrier поддерживает barrierAction. Также стоит упомянуть Phaser (Java 7) как более гибкую альтернативу обоим синхронизаторам.