1. 深入理解 Java 的 CountDownLatch
第一次接触 CountDownLatch 是在处理一个分布式任务调度系统时。当时需要确保所有子任务都完成后再进行数据汇总,尝试了各种线程同步方式都不够优雅,直到发现了这个"倒计时门闩"。它就像运动场上的起跑器,所有运动员(线程)准备就绪后,发令枪(计数器归零)才会响起。
CountDownLatch 是 Java 并发包中的经典工具,特别适合"主从线程协同"的场景。不同于简单的 Thread.join(),它能以更灵活的方式控制线程执行顺序,且不要求创建线程的代码与等待线程的代码耦合在一起。下面我将结合 5 年使用经验,详细解析这个看似简单却内涵丰富的并发工具。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CountDownLatch 核心机制解析
2.1 计数器工作原理
CountDownLatch 的核心是一个 volatile 修饰的 int 类型计数器。这个设计非常巧妙:
- 初始化值:通过构造函数设置,表示需要等待的事件数量
- 递减操作:countDown() 方法使用 CAS(Compare-And-Swap)保证原子性
- 零值触发:当计数器归零时,所有等待线程被唤醒
重要提示:计数器一旦归零就无法重置,这是与 CyclicBarrier 的关键区别。如果需要重复使用,应该选择 CyclicBarrier。
2.2 内存可见性保障
内部使用 AbstractQueuedSynchronizer (AQS) 实现,这带来了两个关键特性:
- happens-before 关系:countDown() 调用前的操作对 await() 返回后的操作可见
- 公平性保证:等待线程按 FIFO 顺序被唤醒
java复制// 典型的内存可见性示例
CountDownLatch latch = new CountDownLatch(1);
Thread writer = new Thread(() -> {
sharedData = 42; // 写操作
latch.countDown();
});
Thread reader = new Thread(() -> {
latch.await();
System.out.println(sharedData); // 保证读取到42
});
3. 实战应用场景与最佳实践
3.1 典型使用模式
3.1.1 主从线程协同
java复制// 电商订单处理示例
CountDownLatch latch = new CountDownLatch(3);
executor.execute(() -> {
inventoryService.checkStock(); // 库存检查
latch.countDown();
});
executor.execute(() -> {
paymentService.processPayment(); // 支付处理
latch.countDown();
});
executor.execute(() -> {
logisticsService.bookShipping(); // 物流预约
latch.countDown();
});
latch.await(5, TimeUnit.SECONDS); // 带超时等待
orderService.confirmOrder(); // 最终确认
3.1.2 多阶段任务控制
java复制// 大数据处理中的两阶段提交
CountDownLatch phase1 = new CountDownLatch(10);
CountDownLatch phase2 = new CountDownLatch(10);
// 第一阶段:数据准备
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
prepareData();
phase1.countDown();
});
}
phase1.await(); // 等待所有准备完成
// 第二阶段:数据处理
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
processData();
phase2.countDown();
});
}
phase2.await(); // 等待所有处理完成
generateReport();
3.2 性能优化技巧
- 合理设置初始值:计数器值应与实际任务数严格一致
- 避免过度等待:总是使用带超时的 await() 方法
- 异常处理:确保 countDown() 在 finally 块中执行
java复制// 优化后的代码结构
ExecutorService executor = Executors.newFixedThreadPool(N);
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
