这 5 个工具都来自java.util.concurrent包,本质都是解决多线程协作同步问题,但分工完全不同:
-
有的管「一次性等大家都做完再走」
-
有的管「循环多次集合再出发」
-
有的管「最多同时多少人干活」
-
有的管「一堆异步任务全完成再下一步」
-
有的管「复杂多阶段动态同步」
一、CountDownLatch:一次性倒计时计数器
1. 功能
它就是一个一次性的倒计时发令枪。主线程设置一个倒计时数,每个子线程完成任务就按一下倒计时,等倒计时归 0,主线程才会继续往下执行。
生活化例子:跑步比赛,裁判(主线程)设定有 10 个运动员(计数 = 10),每个运动员冲过终点就倒计时减 1,等所有人都跑完,裁判才宣布比赛结束、统计成绩。
2. 底层原理
CountDownLatch 底层完全基于 AQS(AbstractQueuedSynchronizer) 实现,是 AQS 共享模式的经典应用:
-
初始化时,给 AQS 的
state变量赋值为倒计时数(比如 10); -
子线程调用
countDown()方法,本质是原子性地把 state 减 1; -
主线程调用
await()方法时,如果 state 不等于 0,当前线程就会被丢进 AQS 的等待队列阻塞挂起; -
当 state 被减到 0 时,AQS 会唤醒等待队列里所有阻塞的线程,主线程继续执行。
核心细节:为什么不能复用?因为 state 减到 0 就结束了,没有内置的重置逻辑,用完就废了,这是它和 CyclicBarrier 最本质的区别。
3. 优缺点
|--------------------|------------------------|
| 优点 | 缺点 |
| 轻量简单,API 极简,上手零成本 | 只能一次性使用,计数用完就失效,无法重置 |
| 基于 AQS 原生实现,性能稳定可靠 | 只能主线程等子线程,不支持子线程之间互相等待 |
| 支持超时等待,避免永久阻塞 | 不能动态调整计数,初始化后就不能改数量 |
4. 选型思考
适合一次性、单阶段的多任务等待场景,比如服务启动时等待多个资源初始化、批量任务执行完汇总结果。
选型原则:只是一次性等任务完成,优先选它,最轻量最稳定;需要循环使用就直接排除。
二、CyclicBarrier:循环同步栅栏
1. 功能
它是一个可循环使用的集合栅栏。一组线程互相等待,所有线程都到达栅栏位置后,才能一起往下走;走完一轮后栅栏自动重置,下一轮还能继续用。
生活化例子:团建大巴车,规定 10 个人到齐了就发车去下一个景点;到景点后大家自由活动,回来集合再去下一个景点,循环使用。
2. 底层原理
和 CountDownLatch 不同,CyclicBarrier 底层不用 AQS ,而是基于ReentrantLock + Condition实现:
-
内部维护一个
count计数器,初始化时设置参与线程数; -
每个线程调用
await()时,先加锁,然后 count 减 1; -
如果 count 没到 0,就调用 Condition 的
await(),把当前线程丢进条件等待队列阻塞; -
当 count 减到 0 时,执行可选的
barrierAction(栅栏触发动作),然后调用 Condition 的signalAll()唤醒所有等待的线程,同时重置 count 为初始值,开启下一轮。
核心细节:它是线程之间互相等,不是主线程等子线程;而且自带重置逻辑,可循环使用,这是和 CountDownLatch 的核心差异。
3. 优缺点
|-------------------------------|--------------------------------|
| 优点 | 缺点 |
| 可循环复用,一轮结束自动重置,适合多阶段任务 | 只能固定线程数,初始化后不能动态增减参与线程 |
| 支持 barrierAction,集齐后自动执行自定义逻辑 | 比 CountDownLatch 重,有锁开销,性能略低 |
| 线程之间互相等待,适合多线程协同计算 | 只要有一个线程中断 / 超时,整个栅栏就会破掉,所有线程报错 |
4. 选型思考
适合多阶段、循环式的并行计算场景,比如并行数据处理、分阶段批量计算。
选型原则:需要循环多次同步、线程之间互相等待选它;一次性场景别用,浪费性能。
三、Semaphore:信号量 / 许可证限流
1. 功能
它是一个流量控制器,本质是管理一组 "许可证",控制同时执行任务的线程最大数量。线程执行前先拿许可证,拿到才能干活,干完还回去;许可证没了,后面的线程就阻塞等待。
生活化例子:停车场只有 5 个车位(5 个许可证),最多同时停 5 辆车;车开出去归还车位,后面的车才能进去停。
2. 底层原理
Semaphore 也是基于AQS 共享模式实现,是共享锁的典型应用:
-
初始化时,AQS 的
state值代表许可证的总数量; -
线程调用
acquire():尝试原子性地把 state 减对应数量,如果减完 state≥0,说明拿到许可证,直接通行;如果 state 不够,就进 AQS 等待队列阻塞; -
线程调用
release():原子性地把 state 加对应数量(归还许可证),同时唤醒等待队列里的线程去抢许可证。
核心细节:支持公平 / 非公平模式:非公平模式直接抢,性能高;公平模式按排队顺序来,不插队但性能低。另外注意:它不校验 "是谁释放的",一个线程可以多次释放,使用不当会导致许可证越来越多。
3. 优缺点
|---------------------|------------------------|
| 优点 | 缺点 |
| 精准控制并发数量,是限流的标准工具 | 不支持线程优先级,高优先级任务也得排队 |
| 支持公平 / 非公平模式,适配不同场景 | 单个线程可多次释放许可证,误用容易出 bug |
| 可动态增减许可证数量,灵活调整流量 | 只有数量控制,没有阶段同步、循环能力 |
4. 选型思考
适合流量控制、资源池管控场景,比如接口限流、数据库连接池、线程池并发数控制。
选型原则:要限制同时运行的线程数、做限流,直接选它,这是领域专属工具。
四、CompletableFuture.allOf ():异步任务全完成触发器
1. 功能
它是异步任务的组合器 ,专门用来管理一堆异步任务:等所有异步任务都执行完成后,再触发后续的动作。它和前面三个工具本质不同:前面三个都是阻塞同步工具 (线程挂起等待),而它是异步非阻塞的,靠回调驱动,不浪费线程。
生活化例子:你点外卖同时下单了奶茶、汉堡、炸鸡,三个商家分别配送(三个异步任务),等三个都送到了,你才开始吃饭(后续动作)。
2. 底层原理
allOf()是 CompletableFuture 的静态组合方法,基于异步回调 + 计数机制实现,完全不依赖 AQS:
-
内部维护一个剩余任务计数器,初始值为传入的 Future 数量;
-
给每个传入的 CompletableFuture 都注册一个回调:不管任务成功还是异常,完成后就把计数器原子性减 1;
-
当计数器减到 0 时,自动触发 allOf 返回的那个 CompletableFuture 的完成事件,后续的
thenApply、whenComplete等回调就会执行。
核心细节:任意一个任务异常,allOf 都会标记为异常;它只等待所有任务完成,不返回结果,要结果还得自己逐个 get。
3. 优缺点
|--------------------------|------------------------|
| 优点 | 缺点 |
| 异步非阻塞,不占用线程资源,性能远高于阻塞式工具 | 调试难度高,异步回调栈不直观,排查问题麻烦 |
| 支持链式调用、任务组合,编排能力极强 | 异常处理复杂,多个任务异常时处理起来麻烦 |
| 适配 IO 密集型场景,充分利用线程资源 | 过度使用容易出现 "回调地狱",代码可读性差 |
4. 选型思考
适合多服务并行调用、异步任务编排、响应式编程场景,比如微服务聚合接口、批量异步数据处理。
选型原则:IO 密集型异步编排优先选它,别用同步工具做异步场景,会白白浪费大量线程资源。
五、Phaser:多阶段同步器(全能选手)
1.功能
它是功能最强的同步工具,相当于 CountDownLatch + CyclicBarrier 的升级超级版。支持多阶段同步、支持动态增减参与线程数、支持循环、支持分层同步,专门应对复杂的多阶段并行任务。
生活化例子:项目分 3 个阶段,第一阶段 10 个人做需求,第二阶段 8 个人做开发,第三阶段 5 个人做测试;每个阶段所有人都完成了,才能进入下一阶段,每个阶段人数还不一样。
2. 底层原理
Phaser 还是基于AQS实现,但设计比前面几个都复杂,核心维护两个关键变量:
-
phase:当前处于第几阶段,完成一个阶段就加 1; -
parties:当前阶段参与的线程数量,支持动态注册 / 注销。
核心执行逻辑:
-
线程调用
arriveAndAwaitAdvance(),表示自己到达当前阶段,等待其他人; -
当所有线程都到达(到达数等于 parties),就进入下一阶段(phase+1),唤醒所有等待的线程;
-
支持
register()动态加人、deregister()动态减人,不用重新初始化。
核心细节:还支持分层 Phaser(父子 Phaser)、支持终止相位,功能极其全面,是 Java 并发包里最复杂的同步工具。
3. 优缺点
|------------------------------|--------------------------|
| 优点 | 缺点 |
| 功能最全,动态调整线程数、多阶段、可循环、可分层 | 太重太复杂,学习成本高,简单场景用属于杀鸡用牛刀 |
| 比 CyclicBarrier 灵活,支持动态增减参与方 | 性能略低于轻量工具,逻辑复杂容易用错 |
| 支持分层嵌套,适配复杂流水线任务 | 排查问题难度高,状态多、逻辑分支多 |
4. 选型思考
适合复杂多阶段、动态线程数的并行任务,比如流水线数据处理、分阶段作业调度。
选型原则:简单场景别碰它,CountDownLatch 和 CyclicBarrier 能搞定就不用它;只有动态多阶段场景才考虑。
六、整体对比与终极选型指南
1. 核心差异对照表
|-------------------------|-----------|-------|-------|-------------------------|---------------|-----|
| 工具 | 核心功能 | 是否可复用 | 是否阻塞 | 底层机制 | 核心场景 | 复杂度 |
| CountDownLatch | 一次性倒计时等待 | ❌ 一次性 | ✅ 阻塞 | AQS 共享模式 | 一次性多任务等待、启动汇总 | 极低 |
| CyclicBarrier | 循环线程互等 | ✅ 可循环 | ✅ 阻塞 | ReentrantLock+Condition | 多阶段循环并行计算 | 低 |
| Semaphore | 并发数量限流 | ✅ 可复用 | ✅ 阻塞 | AQS 共享模式 | 接口限流、资源池管控 | 低 |
| CompletableFuture.allOf | 异步任务全完成组合 | ✅ 可组合 | ❌ 非阻塞 | 异步回调机制 | 异步任务编排、多服务并行 | 中 |
| Phaser | 多阶段动态同步 | ✅ 多阶段 | ✅ 阻塞 | AQS 阶段计数 | 复杂多阶段流水线任务 | 高 |
2. 选型口诀
-
一次性等待任务 → 选 CountDownLatch,最轻量最稳妥
-
循环多阶段互等 → 选 CyclicBarrier,标准循环同步
-
限流控并发数量 → 选 Semaphore,流量控制专属
-
异步编排多任务 → 选 CompletableFuture.allOf,非阻塞性能高
-
复杂多阶段动态任务 → 选 Phaser,功能最全最灵活
核心原则:能用简单工具就不用复杂工具,满足需求的前提下,越简单越稳定、越容易维护。