CountDownLatch 与 CyclicBarrier
两者都是 JUC 包中的同步工具,用于协调线程执行。
CountDownLatch
作用:一个线程等待其他线程完成。计数器递减,归零后释放等待线程。
构造方法
// 计数器初始值为 N
CountDownLatch latch = new CountDownLatch(3);主要方法
| 方法 | 说明 |
|---|---|
| countDown() | 计数减 1 |
| await() | 阻塞直到计数为 0 |
| await(timeout, unit) | 超时等待 |
| getCount() | 获取当前计数 |
典型场景:主线程等待子线程
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行任务
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数减 1
}
}).start();
}
latch.await(); // 阻塞,直到计数为 0
System.out.println("所有子线程完成,主线程继续");
}
}典型场景:服务启动检测
public class ServiceStartup {
private CountDownLatch latch = new CountDownLatch(3);
public void start() throws Exception {
// 启动数据库连接
new Thread(() -> {
initDatabase();
latch.countDown();
}).start();
// 启动缓存服务
new Thread(() -> {
initCache();
latch.countDown();
}).start();
// 启动消息队列
new Thread(() -> {
initMQ();
latch.countDown();
}).start();
// 等待所有服务就绪
latch.await(30, TimeUnit.SECONDS);
System.out.println("所有服务启动完成");
}
}CyclicBarrier
作用:线程互相等待,都到达屏障后一起继续执行。可循环使用。
构造方法
// 屏障点线程数
CyclicBarrier barrier = new CyclicBarrier(3);
// 带栅栏动作
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障");
});主要方法
| 方法 | 说明 |
|---|---|
| await() | 到达屏障并等待 |
| await(timeout, unit) | 超时等待 |
| reset() | 重置屏障 |
| getNumberWaiting() | 获取等待线程数 |
| isBroken() | 屏障是否被破坏 |
典型场景:分阶段任务
public class CyclicBarrierDemo {
public static void main(String[] args) {
int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
System.out.println("=== 阶段完成 ===");
});
for (int i = 0; i < parties; i++) {
new Thread(() -> {
try {
for (int phase = 1; phase <= 3; phase++) {
System.out.println(Thread.currentThread().getName() + " 阶段" + phase + " 执行");
Thread.sleep(500);
barrier.await(); // 等待其他线程
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}输出:
Thread-0 阶段1 执行
Thread-1 阶段1 执行
Thread-2 阶段1 执行
=== 阶段完成 ===
Thread-0 阶段2 执行
Thread-1 阶段2 执行
Thread-2 阶段2 执行
=== 阶段完成 ===
...典型场景:批量处理
public class BatchProcessor {
private final CyclicBarrier barrier;
private final List<Data> dataList;
private final List<Result> results = new CopyOnWriteArrayList<>();
public BatchProcessor(int parties, List<Data> dataList) {
this.dataList = dataList;
this.barrier = new CyclicBarrier(parties, this::saveBatch);
}
public void process() {
int batchSize = dataList.size() / barrier.getParties();
for (int i = 0; i < barrier.getParties(); i++) {
int start = i * batchSize;
int end = Math.min(start + batchSize, dataList.size());
int finalI = i;
new Thread(() -> {
for (int j = start; j < end; j++) {
results.add(process(dataList.get(j)));
}
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
private void saveBatch() {
// 保存结果到数据库
System.out.println("保存 " + results.size() + " 条结果");
}
}两者对比
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 作用 | 一个线程等待其他线程 | 线程互相等待 |
| 重用性 | 一次性,不可重置 | 可循环使用 |
| 计数方向 | 递减(countDown) | 递增(await) |
| 触发条件 | 计数归零 | 达到指定线程数 |
| 等待者 | 调用 await 的线程 | 所有参与线程 |
| 异常影响 | 不影响其他线程 | 一个中断,全抛异常 |
| 典型场景 | 主线程等子线程完成 | 分阶段并行任务 |
选择建议
| 场景 | 推荐 |
|---|---|
| 主线程等待 N 个子任务完成 | CountDownLatch |
| N 个线程分阶段执行 | CyclicBarrier |
| 只需要执行一次 | CountDownLatch |
| 需要循环执行多次 | CyclicBarrier |
| 任务间无协作 | CountDownLatch |
| 任务间需要同步点 | CyclicBarrier |
常见面试题
Q1: CountDownLatch 为什么不能重用?
设计目的不同。CountDownLatch 用于一次性事件,如服务启动等待。如果需要重用,应选择 CyclicBarrier。
Q2: CyclicBarrier 如何处理异常?
一个线程中断或超时,屏障会被破坏,所有等待线程抛出 BrokenBarrierException:
try {
barrier.await(5, TimeUnit.SECONDS);
} catch (BrokenBarrierException | TimeoutException e) {
barrier.reset(); // 重置屏障
}Q3: 计数器和屏障数量如何设置?
- CountDownLatch:计数 = 需要等待的线程数
- CyclicBarrier:parties = 参与协作的线程数
Q4: 可以在 CountDownLatch 中使用多个 await 吗?
可以,多个线程可以同时 await,计数归零后全部释放。
小结
- CountDownLatch:一次性,一个线程等待其他线程,计数递减
- CyclicBarrier:可循环,线程互相等待,计数递增
- 选择依据:是否需要重用、是单向等待还是互相等待