说说 CountDownLatch 原理
CountDownLatch 解决的是一个非常具体的并发协调问题:一个(或多个)线程需要等待另外 N 个线程全部完成后,才能继续执行。典型如首页聚合接口:并行调用用户服务、订单服务、推荐服务,三个都返回后才能组装响应。本文从用法到 AQS 源码层把原理讲透。
用法模型
CountDownLatch latch = new CountDownLatch(3); |
两个核心方法:countDown() 把计数器减一(减到 0 时释放所有等待者);await() 让当前线程阻塞直到计数器为 0。注意 countDown 必须放在 finally 里——任务抛异常若不计数,await 将永久阻塞,这是生产上最常见的 CountDownLatch 事故。
原理:AQS 共享模式
CountDownLatch 内部是一个继承 AQS(AbstractQueuedSynchronizer)的 Sync:
- state 即计数器:构造时
setState(N) countDown()→releaseShared(1)→ CAS 把 state 减一;减到 0 时返回 true,触发doReleaseShared()唤醒等待队列中所有线程await()→acquireSharedInterruptibly(1):state 不为 0 就把当前线程包装成节点加入 CLH 等待队列并 park 阻塞;被唤醒后重新检查 state,为 0 才通过
关键点在于它是共享模式:与 ReentrantLock 的独占模式不同,共享模式允许”一次释放、唤醒全部等待线程”,这正是”门闩打开、所有人通过”的语义。
与 CyclicBarrier 的区别
| 维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 等待关系 | 一个线程等 N 个事件 | N 个线程互相等待到齐 |
| 复用 | 一次性,归零后失效 | 可循环重用(reset) |
| 回调 | 无 | 到齐时可执行 barrierAction |
| 实现 | AQS 共享模式 | ReentrantLock + Condition |
选型口诀:等”事情做完”用 latch,等”人到齐一起出发”用 barrier。
几个容易踩的坑
- 计数不匹配:new 了 3 但只提交 2 个任务(或某任务异常跳过 countDown),await 线程死等。建议 await 用带超时版本
await(timeout, unit)兜底 - 重复使用:归零后再 countDown 无效、再 await 直接通过,需要重复协调应换 CyclicBarrier 或每次新建 latch
- await 线程被中断:抛 InterruptedException,需决定是重试还是降级返回部分结果
延伸:CompletableFuture 的替代
JDK8 之后,同样的聚合场景更推荐 CompletableFuture:
CompletableFuture.allOf( |
它把”等待+取结果”合为一体,还自带异常传播与编排能力。CountDownLatch 的价值在于语义直白、无返回值协调(如等待 N 个初始化动作完成)时依然最清晰。
理解 CountDownLatch 的关键是抓住”AQS 共享模式 + state 作计数器”这一本质:其余 API 都只是这两个事实的包装。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











