5种Java并发同步工具全解

这 5 个工具都来自java.util.concurrent包,本质都是解决多线程协作同步问题,但分工完全不同:

  • 有的管「一次性等大家都做完再走」

  • 有的管「循环多次集合再出发」

  • 有的管「最多同时多少人干活」

  • 有的管「一堆异步任务全完成再下一步」

  • 有的管「复杂多阶段动态同步」

一、CountDownLatch:一次性倒计时计数器

1. 功能

它就是一个一次性的倒计时发令枪。主线程设置一个倒计时数,每个子线程完成任务就按一下倒计时,等倒计时归 0,主线程才会继续往下执行。

生活化例子:跑步比赛,裁判(主线程)设定有 10 个运动员(计数 = 10),每个运动员冲过终点就倒计时减 1,等所有人都跑完,裁判才宣布比赛结束、统计成绩。

2. 底层原理

CountDownLatch 底层完全基于 AQS(AbstractQueuedSynchronizer) 实现,是 AQS 共享模式的经典应用:

  1. 初始化时,给 AQS 的state变量赋值为倒计时数(比如 10);

  2. 子线程调用countDown()方法,本质是原子性地把 state 减 1

  3. 主线程调用await()方法时,如果 state 不等于 0,当前线程就会被丢进 AQS 的等待队列阻塞挂起;

  4. 当 state 被减到 0 时,AQS 会唤醒等待队列里所有阻塞的线程,主线程继续执行。

核心细节:为什么不能复用?因为 state 减到 0 就结束了,没有内置的重置逻辑,用完就废了,这是它和 CyclicBarrier 最本质的区别。

3. 优缺点

|--------------------|------------------------|
| 优点 | 缺点 |
| 轻量简单,API 极简,上手零成本 | 只能一次性使用,计数用完就失效,无法重置 |
| 基于 AQS 原生实现,性能稳定可靠 | 只能主线程等子线程,不支持子线程之间互相等待 |
| 支持超时等待,避免永久阻塞 | 不能动态调整计数,初始化后就不能改数量 |

4. 选型思考

适合一次性、单阶段的多任务等待场景,比如服务启动时等待多个资源初始化、批量任务执行完汇总结果。

选型原则:只是一次性等任务完成,优先选它,最轻量最稳定;需要循环使用就直接排除。

二、CyclicBarrier:循环同步栅栏

1. 功能

它是一个可循环使用的集合栅栏。一组线程互相等待,所有线程都到达栅栏位置后,才能一起往下走;走完一轮后栅栏自动重置,下一轮还能继续用。

生活化例子:团建大巴车,规定 10 个人到齐了就发车去下一个景点;到景点后大家自由活动,回来集合再去下一个景点,循环使用。

2. 底层原理

和 CountDownLatch 不同,CyclicBarrier 底层不用 AQS ,而是基于ReentrantLock + Condition实现:

  1. 内部维护一个count计数器,初始化时设置参与线程数;

  2. 每个线程调用await()时,先加锁,然后 count 减 1;

  3. 如果 count 没到 0,就调用 Condition 的await(),把当前线程丢进条件等待队列阻塞;

  4. 当 count 减到 0 时,执行可选的barrierAction(栅栏触发动作),然后调用 Condition 的signalAll()唤醒所有等待的线程,同时重置 count 为初始值,开启下一轮。

核心细节:它是线程之间互相等,不是主线程等子线程;而且自带重置逻辑,可循环使用,这是和 CountDownLatch 的核心差异。

3. 优缺点

|-------------------------------|--------------------------------|
| 优点 | 缺点 |
| 可循环复用,一轮结束自动重置,适合多阶段任务 | 只能固定线程数,初始化后不能动态增减参与线程 |
| 支持 barrierAction,集齐后自动执行自定义逻辑 | 比 CountDownLatch 重,有锁开销,性能略低 |
| 线程之间互相等待,适合多线程协同计算 | 只要有一个线程中断 / 超时,整个栅栏就会破掉,所有线程报错 |

4. 选型思考

适合多阶段、循环式的并行计算场景,比如并行数据处理、分阶段批量计算。

选型原则:需要循环多次同步、线程之间互相等待选它;一次性场景别用,浪费性能。

三、Semaphore:信号量 / 许可证限流

1. 功能

它是一个流量控制器,本质是管理一组 "许可证",控制同时执行任务的线程最大数量。线程执行前先拿许可证,拿到才能干活,干完还回去;许可证没了,后面的线程就阻塞等待。

生活化例子:停车场只有 5 个车位(5 个许可证),最多同时停 5 辆车;车开出去归还车位,后面的车才能进去停。

2. 底层原理

Semaphore 也是基于AQS 共享模式实现,是共享锁的典型应用:

  1. 初始化时,AQS 的state值代表许可证的总数量;

  2. 线程调用acquire():尝试原子性地把 state 减对应数量,如果减完 state≥0,说明拿到许可证,直接通行;如果 state 不够,就进 AQS 等待队列阻塞;

  3. 线程调用release():原子性地把 state 加对应数量(归还许可证),同时唤醒等待队列里的线程去抢许可证。

核心细节:支持公平 / 非公平模式:非公平模式直接抢,性能高;公平模式按排队顺序来,不插队但性能低。另外注意:它不校验 "是谁释放的",一个线程可以多次释放,使用不当会导致许可证越来越多。

3. 优缺点

|---------------------|------------------------|
| 优点 | 缺点 |
| 精准控制并发数量,是限流的标准工具 | 不支持线程优先级,高优先级任务也得排队 |
| 支持公平 / 非公平模式,适配不同场景 | 单个线程可多次释放许可证,误用容易出 bug |
| 可动态增减许可证数量,灵活调整流量 | 只有数量控制,没有阶段同步、循环能力 |

4. 选型思考

适合流量控制、资源池管控场景,比如接口限流、数据库连接池、线程池并发数控制。

选型原则:要限制同时运行的线程数、做限流,直接选它,这是领域专属工具。

四、CompletableFuture.allOf ():异步任务全完成触发器

1. 功能

它是异步任务的组合器 ,专门用来管理一堆异步任务:等所有异步任务都执行完成后,再触发后续的动作。它和前面三个工具本质不同:前面三个都是阻塞同步工具 (线程挂起等待),而它是异步非阻塞的,靠回调驱动,不浪费线程。

生活化例子:你点外卖同时下单了奶茶、汉堡、炸鸡,三个商家分别配送(三个异步任务),等三个都送到了,你才开始吃饭(后续动作)。

2. 底层原理

allOf()是 CompletableFuture 的静态组合方法,基于异步回调 + 计数机制实现,完全不依赖 AQS:

  1. 内部维护一个剩余任务计数器,初始值为传入的 Future 数量;

  2. 给每个传入的 CompletableFuture 都注册一个回调:不管任务成功还是异常,完成后就把计数器原子性减 1;

  3. 当计数器减到 0 时,自动触发 allOf 返回的那个 CompletableFuture 的完成事件,后续的thenApplywhenComplete等回调就会执行。

核心细节:任意一个任务异常,allOf 都会标记为异常;它只等待所有任务完成,不返回结果,要结果还得自己逐个 get。

3. 优缺点

|--------------------------|------------------------|
| 优点 | 缺点 |
| 异步非阻塞,不占用线程资源,性能远高于阻塞式工具 | 调试难度高,异步回调栈不直观,排查问题麻烦 |
| 支持链式调用、任务组合,编排能力极强 | 异常处理复杂,多个任务异常时处理起来麻烦 |
| 适配 IO 密集型场景,充分利用线程资源 | 过度使用容易出现 "回调地狱",代码可读性差 |

4. 选型思考

适合多服务并行调用、异步任务编排、响应式编程场景,比如微服务聚合接口、批量异步数据处理。

选型原则:IO 密集型异步编排优先选它,别用同步工具做异步场景,会白白浪费大量线程资源。

五、Phaser:多阶段同步器(全能选手)

1.功能

它是功能最强的同步工具,相当于 CountDownLatch + CyclicBarrier 的升级超级版。支持多阶段同步、支持动态增减参与线程数、支持循环、支持分层同步,专门应对复杂的多阶段并行任务。

生活化例子:项目分 3 个阶段,第一阶段 10 个人做需求,第二阶段 8 个人做开发,第三阶段 5 个人做测试;每个阶段所有人都完成了,才能进入下一阶段,每个阶段人数还不一样。

2. 底层原理

Phaser 还是基于AQS实现,但设计比前面几个都复杂,核心维护两个关键变量:

  • phase:当前处于第几阶段,完成一个阶段就加 1;

  • parties:当前阶段参与的线程数量,支持动态注册 / 注销。

核心执行逻辑:

  1. 线程调用arriveAndAwaitAdvance(),表示自己到达当前阶段,等待其他人;

  2. 当所有线程都到达(到达数等于 parties),就进入下一阶段(phase+1),唤醒所有等待的线程;

  3. 支持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,功能最全最灵活

核心原则:能用简单工具就不用复杂工具,满足需求的前提下,越简单越稳定、越容易维护。

相关推荐
十五喵源码网15 分钟前
基于SpringBoot2+vue2的健身房管理系统
java·毕业设计·springboot·论文笔记
2601_9620566018 分钟前
可造成敏感信息泄露!Spring Boot之Actuator信息泄露漏洞三种利用方式总结
java·spring boot·后端
garmin Chen1 小时前
redis面试题
java·数据库·redis·缓存
chen<>2 小时前
C++ 六种 memory_order:从原子性到线程间可见性.md
java·linux·开发语言·c++
十年Java程序媛2 小时前
SpringBoot3 + JDK17 实战复盘:HikariCP 连接耗尽,虚拟线程场景额外注意事项
java·spring boot
二十雨辰2 小时前
[Java]-JVM面试题
java·开发语言·jvm
cfm_29142 小时前
ConcurrentHashMap 线程安全机制与 JDK 1.8 演进
java·开发语言·安全
tqs_123452 小时前
值传递与引用传递
java·开发语言·python
2601_962181962 小时前
Spring boot从0到1 - day01
java·spring boot·后端