在Java中如何使用CountDownLatch_Java并发协作工具解析

CountDownLatch 是一个一次性同步辅助类,用于让一个或多个线程等待其他线程完成指定数量的操作;适用于主线程等待多个子任务(如批量 HTTP 请求)全部完成后再汇总结果的场景。

CountDownLatch 是什么,什么时候该用它

CountDownLatch 不是锁,而是一个同步辅助类,用来让一个或多个线程等待其他线程完成一组操作。典型场景是:主线程启动若干子任务(比如 5 个 HTTP 请求),等它们全部返回后再汇总结果——这时 CountDownLatch 比轮询或 join() 更可控、更轻量。

它内部维护一个计数器,初始化时指定值;每次调用 countDown() 就减 1;调用 await() 的线程会阻塞,直到计数器归零或被中断。

初始化和基本用法要注意哪些坑

最常见错误是把 CountDownLatch 初始化为 0:此时 await() 不会阻塞,直接返回,导致逻辑提前执行。另一个坑是重复调用 countDown() 超过初始值,虽然不会报错,但可能掩盖逻辑错误(比如本该只触发一次的清理动作被多执行)。

  • 计数值必须大于 0 才有意义,new CountDownLatch(0) 适合“立即放行”场景,但需明确意图
  • 每个参与协作的线程应且仅调用一次 countDown(),建议放在 finally 块里防异常跳过
  • await() 可带超时参数,避免无限等待;返回 false 表示超时,需主动处理失败路径

和 CyclicBarrier、Semaphore 的关键区别在哪

三者都用于线程协作,但语义不同:CyclicBarrier 是“大家一起到齐才出发”,可重用;Semaphore 控制并发数,类似限流器;而 CountDownLatch 是“等别人干完我再动”,一次性且不可重置。

如果需要多次等待同一组事件(比如每轮批处理都等 10 个任务完成),别硬用 CountDownLatch —— 它不能 reset,得换 CyclicBarrier 或手动 new 新实例(不推荐,易泄漏)。

  • CountDownLatch:单次倒计时,不可重用
  • CyclicBarrier:可重复使用,支持到达时触发回调(Runnable
  • Semaphore:控制同时访问资源的线程数,和“等待完成”无直接关系

一个真实可用的 HTTP 批量请求示例

下面代码模拟 3 个异步请求并发执行,主线程等待全部完成。注意异常处理、超时控制和资源释放:

CountDownLatch latch = new CountDownLatch(3);
List results = Collections.synchronizedList(new ArrayList<>());

for (int i = 0; i < 3; i++) {
    int taskId = i;
    new Thread(() -> {
        try {
            // 模拟网络请求
            String result = "response-" + taskId;
            Thread.sleep(1000L);
            results.add(result);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            latch.countDown(); // 确保一定执行
        }
    }).start();
}

try {
    if (!latch.await(5, TimeUnit.SECONDS)) {
        System.err.println("Timeout waiting for all tasks");
        return;
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    return;
}

System.out.println("All done: " + results);

实际项目中,更推荐用 CompletableFuture 替代手写 CountDownLatch,尤其在需要链式处理、异常传播或组合多个异步结果时。但理解

CountDownLatch 的底层协作模型,对排查并发问题仍有直接帮助。