← Назад к вопросам
В чём разница между CyclicBarrier и CountDownLatch?
2.0 Middle🔥 191 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между CyclicBarrier и CountDownLatch
Оба класса используются для синхронизации потоков, но они решают разные задачи. Давайте разберем различия и примеры использования.
Сравнительная таблица
| Параметр | CountDownLatch | CyclicBarrier |
|---|---|---|
| Цель | Главный поток ждет 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(); // ждет остальных и переходит в следующую фазу