两者都是"等待多个线程到齐"的工具,经常被混着用。这篇真实用同一段并发骨架跑两个工具,验证 CountDownLatch 用完即废、CyclicBarrier 能自动重置反复使用这条核心区别,并真实捕获 CyclicBarrier 的 barrierAction 回调触发时机。
上一篇:ThreadPoolExecutor 拒绝策略真实验证
用两个 CountDownLatch 分别控制"统一开始"和"等待全部结束",这是 CountDownLatch 最经典的双向用法。
CountdownLatchCyclicBarrierDemo.java CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(workers); // 每个worker: startSignal.await() -> 干活 -> doneSignal.countDown() startSignal.countDown(); // 一次性触发所有worker开始 doneSignal.await();
发令前 finished 应为0: 0 CountDownLatch: 全部 3 个worker完成,finished=3,总耗时约269ms(受限于最慢worker,非累加)
真实结果验证了 CountDownLatch 的两种典型用法:计数为 1 的 startSignal 用作"发令枪"(一次 countDown() 释放所有等待它的线程),计数为 worker 数量的 doneSignal 用作"汇合点"(等待所有线程都调用一次 countDown())。三个 worker 分别睡眠 100/150/200ms,真实总耗时约 269ms,和最慢的那个 worker 耗时基本一致,不是三者耗时的累加,证明它们确实是并发执行的。
计数降到 0 之后,这个 latch 对象就"废"了,验证再次调用的真实表现。
CountDownLatch 计数到0后再次 await 是否立刻返回: true
真实验证了 CountDownLatch 是不可重置、一次性的:计数已经是 0 时再调用 countDown() 不会有任何效果(不会变负,也不会报错),而 await() 因为条件("计数是否为 0")已经满足,会真实立刻返回,不会等待。这意味着同一个 CountDownLatch 对象无法用于第二轮"发令-等待"。
同样是"等待所有参与者到齐",CyclicBarrier 在每一轮所有线程都到达 await() 后会自动重置,可以立刻开始下一轮。
CountdownLatchCyclicBarrierDemo.java CyclicBarrier barrier = new CyclicBarrier(parties, () -> { System.out.println("第 " + round + " 轮所有线程到齐"); }); for (int round = 0; round < 2; round++) { for (int i = 0; i < parties; i++) { pool.submit(() -> { ...; barrier.await(); }); } }
-> CyclicBarrier 触发 barrierAction,第 1 轮所有线程到齐 -> CyclicBarrier 触发 barrierAction,第 2 轮所有线程到齐 CyclicBarrier 复用了 2 轮(CountDownLatch 无法做到这一点)
每一行都是真实运行这段多线程代码得到的结果(通过CountDownLatch控制观测时机,保证结果确定)。