← Назад к вопросам

В чём разница между CyclicBarrier и CountDownLatch?

2.0 Middle🔥 191 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между CyclicBarrier и CountDownLatch

Оба класса используются для синхронизации потоков, но они решают разные задачи. Давайте разберем различия и примеры использования.

Сравнительная таблица

ПараметрCountDownLatchCyclicBarrier
ЦельГлавный поток ждет N потоковN потоков ждут друг друга
Количество потоковНельзя изменитьМожно переиспользовать
ПереиспользованиеОДНОРАЗОВЫЙ (не переиспользуется)ПЕРЕИСПОЛЬЗУЕМЫЙ (циклический)
Как работаетКаждый поток вызывает countDown()Каждый поток вызывает await()
countDown() vs await()countDown() уменьшает счетчикawait() останавливает поток
Главный потокВызывает await() и ждетУчаствует в барьере как все
Действие при 0Все потоки пробуждаются ОДИН разВсе потоки пробуждаются и цикл повторяется
ИсключениеНет встроенной обработкиЕсть timeout и action

CountDownLatch

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

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        
        // Запускаем 3 работника
        for (int i = 1; i <= 3; i++) {
            new Thread(new Worker("Работник " + i, latch)).start();
        }
        
        System.out.println("Главный поток ждет...");
        latch.await();  // ждем, пока все 3 работника закончат
        System.out.println("Все работники закончили! Продолжаем...");
    }
}

class Worker implements Runnable {
    private String name;
    private CountDownLatch latch;
    
    public Worker(String name, CountDownLatch latch) {
        this.name = name;
        this.latch = latch;
    }
    
    @Override
    public void run() {
        try {
            System.out.println(name + " начал работу");
            Thread.sleep(2000);  // имитация работы
            System.out.println(name + " закончил работу");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown();  // уменьшаем счетчик на 1
        }
    }
}

Вывод:

Работник 1 начал работу
Работник 2 начал работу
Работник 3 начал работу
Главный поток ждет...
Работник 2 закончил работу
Работник 1 закончил работу
Работник 3 закончил работу
Все работники закончили! Продолжаем...

CyclicBarrier

CyclicBarrier — это переиспользуемый барьер, который позволяет N потокам ждать друг друга и затем продолжить вместе.

public class CyclicBarrierExample {
    public static void main(String[] args) {
        // Барьер на 3 потока + action при достижении
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("[Все потоки достигли барьера!]");
        });
        
        // Запускаем 3 потока
        for (int i = 1; i <= 3; i++) {
            new Thread(new Racer("Участник " + i, barrier)).start();
        }
    }
}

class Racer implements Runnable {
    private String name;
    private CyclicBarrier barrier;
    
    public Racer(String name, CyclicBarrier barrier) {
        this.name = name;
        this.barrier = barrier;
    }
    
    @Override
    public void run() {
        try {
            System.out.println(name + " готовится (фаза 1)");
            barrier.await();  // ждем остальных
            
            System.out.println(name + " начинает бежать (фаза 2)");
            barrier.await();  // ждем остальных снова
            
            System.out.println(name + " пересекает финиш (фаза 3)");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

Вывод:

Участник 1 готовится (фаза 1)
Участник 2 готовится (фаза 1)
Участник 3 готовится (фаза 1)
[Все потоки достигли барьера!]
Участник 2 начинает бежать (фаза 2)
Участник 1 начинает бежать (фаза 2)
Участник 3 начинает бежать (фаза 2)
[Все потоки достигли барьера!]
Участник 3 пересекает финиш (фаза 3)
Участник 1 пересекает финиш (фаза 3)
Участник 2 пересекает финиш (фаза 3)

Ключевые различия

1. Направление синхронизации

CountDownLatch: ОДИН поток ждет МНОГО потоков

Поток 1 → countDown()
Поток 2 → countDown()     ┐
Поток 3 → countDown()     ├→ Главный поток пробуждается
Главный → await() (ждет)  ┘

CyclicBarrier: МНОГО потоков ждут друг друга

Поток 1 → await() (ждет)
Поток 2 → await() (ждет) ┐ когда все 3 достигнут
Поток 3 → await() (ждет) ┘ барьера, все продолжают

2. Переиспользование

CountDownLatch одноразовый:

CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await();  // ждет

latch.countDown();  // второй вызов — НЕ влияет
latch.await();  // сразу пробуждается (уже 0)

CyclicBarrier переиспользуемый:

CyclicBarrier barrier = new CyclicBarrier(2);

barrier.await();  // 1-я итерация, ждет
barrier.await();  // 1-я итерация, оба пробуждаются

barrier.await();  // 2-я итерация, ждет заново
barrier.await();  // 2-я итерация, оба пробуждаются снова

Практические примеры использования

CountDownLatch: запуск тестов

public class TestRunner {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(4);
        
        System.out.println("Запускаем 4 теста...");
        
        // Unit тесты
        new Thread(() -> {
            System.out.println("Unit тесты запущены");
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println("Unit тесты готовы");
            latch.countDown();
        }).start();
        
        // Integration тесты
        new Thread(() -> {
            System.out.println("Integration тесты запущены");
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            System.out.println("Integration тесты готовы");
            latch.countDown();
        }).start();
        
        // Performance тесты
        new Thread(() -> {
            System.out.println("Performance тесты запущены");
            try { Thread.sleep(1500); } catch (InterruptedException e) {}
            System.out.println("Performance тесты готовы");
            latch.countDown();
        }).start();
        
        // Smoke тесты
        new Thread(() -> {
            System.out.println("Smoke тесты запущены");
            try { Thread.sleep(500); } catch (InterruptedException e) {}
            System.out.println("Smoke тесты готовы");
            latch.countDown();
        }).start();
        
        latch.await();
        System.out.println("ВСЕ ТЕСТЫ ЗАВЕРШЕНЫ! Публикуем результаты...");
    }
}

CyclicBarrier: синхронный обмен данными

public class DataSynchronizer {
    public static void main(String[] args) {
        int numPhases = 3;
        CyclicBarrier barrier = new CyclicBarrier(2, () -> {
            System.out.println("=> Фаза завершена, переход к следующей\n");
        });
        
        new Thread(new DataWorker("Процесс A", barrier, numPhases)).start();
        new Thread(new DataWorker("Процесс B", barrier, numPhases)).start();
    }
}

class DataWorker implements Runnable {
    private String name;
    private CyclicBarrier barrier;
    private int phases;
    
    public DataWorker(String name, CyclicBarrier barrier, int phases) {
        this.name = name;
        this.barrier = barrier;
        this.phases = phases;
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= phases; i++) {
            try {
                System.out.println(name + " готовит данные фазы " + i);
                Thread.sleep(1000);
                System.out.println(name + " завершил фазу " + i);
                barrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name + " закончил все фазы");
    }
}

Что выбрать?

Используй CountDownLatch если:

  • Главный поток ждет завершения других потоков
  • Нужна одноразовая синхронизация
  • Потоки завершают работу независимо

Используй CyclicBarrier если:

  • Несколько потоков должны ждать друг друга
  • Нужна переиспользуемая синхронизация
  • Потоки должны действовать синхронно в несколько фаз
  • Нужно выполнить action при каждом достижении барьера

Современные альтернативы

Phaser (Java 7+) — более мощная комбинация CountDownLatch и CyclicBarrier:

Phaser phaser = new Phaser(3);  // 3 участника

// Каждый поток
phaser.arriveAndAwaitAdvance();  // ждет остальных и переходит в следующую фазу