В чем заключаются различия между 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) как более гибкую альтернативу обоим синхронизаторам.