Appearance
CountDownLatch、CyclicBarrier、Semaphore 怎么选:JUC 线程协作工具实战理解
很多并发问题,不是“要不要互斥”,而是:
- 某些线程得等别人做完
- 一批线程要同时开始下一阶段
- 同一时刻只能放有限数量的线程进入
这时真正有用的往往不是普通锁,而是线程协作工具。
先说结论
三者可以先这样记:
CountDownLatch:等一批任务做完CyclicBarrier:一批线程彼此等待后再一起继续Semaphore:控制同时允许多少线程进入
它们关注的不是“共享变量保护”,而是“线程之间的协作关系”。
一、CountDownLatch:一个等多个
最典型的场景是:
- 主线程等多个子任务完成后再继续
例如:
- 页面聚合多个远程调用
- 启动阶段等待多个模块初始化
基本理解
初始化一个计数值,每完成一个任务就 countDown() 一次,等待方通过 await() 阻塞,直到计数归零。
特点
- 计数只能减,不能重置
- 更像“一次性门闩”
所以它很适合:
- 一次性等待全部完成
二、CyclicBarrier:多个互相等
它更适合:
- 一批线程都达到某个阶段后,再一起进入下一阶段
例如:
- 并行计算分阶段推进
- 压测时模拟多个线程同时起跑
基本理解
到达屏障点的线程先等待,直到达到指定数量,所有线程一起继续。
特点
- 可以重复使用
- 支持 barrier action
所以它比 CountDownLatch 更偏:
- 多线程“会合”
三、Semaphore:控制并发进入数量
它适合的问题是:
- 某个资源同一时刻只能允许有限数量线程访问
例如:
- 连接池限流
- 接口并发数限制
- 有限资源抢占
基本理解
它维护一组许可:
acquire()获取许可release()归还许可
特点
- 不是互斥锁
- 可以允许多个线程同时进入,只要不超过许可数
如果许可数是 1,它也能退化成近似互斥的效果,但通常不建议把它当普通锁替代品来用。
四、一个更实用的场景对照
场景 1:等 10 个任务全部完成
用:
CountDownLatch
场景 2:3 个线程每轮都要先都准备好,再进入下一轮
用:
CyclicBarrier
场景 3:某个资源最多允许 5 个线程同时访问
用:
Semaphore
五、和锁的区别到底在哪
锁更关注的是:
- 某段临界区同一时刻能不能只有一个线程执行
而协作工具更关注的是:
- 线程什么时候该等
- 线程何时可以一起继续
- 线程进入的数量要不要被控制
所以很多人把这三类工具和锁放在一起记,其实容易混。
六、几个常见坑
1. CountDownLatch 用完还想复用
它是一次性的,归零后不能重置。
2. CyclicBarrier 某个线程异常退出,其他线程一直等
这类问题在并行计算里很常见,所以实际使用时要考虑超时和异常处理。
3. Semaphore 获取了许可却忘记释放
这和锁忘记解锁一样,最终会把资源用死。
一句话总结
这三类工具本质上都在解决“线程怎么配合”:
- 等完成,用
CountDownLatch - 等会合,用
CyclicBarrier - 限并发,用
Semaphore
先把问题类型分清,再选工具,会比记 API 更有用。