CountDownLatch用于一次性等待多个线程完成,计数器归零后不可重置;CyclicBarrier支持多轮循环同步并可执行屏障动作;Semaphore控制并发访问数量;Exchanger和Phaser未展开但同属线程协作工具。

CountDownLatch、CyclicBarrier、Semaphore、Exchanger、Phaser 是 Java java.util.concurrent 包中最常用且最实用的并发工具类,它们不是“替代 synchronized 的锁”,而是解决线程协作问题的专用设施——该用哪个,取决于你到底想“等什么”“控什么”“同步到哪一步”。
什么时候该用 CountDownLatch?别和 CyclicBarrier 搞混
它只做一件事:让一个或多个线程阻塞等待,直到其他线程调用 countDown() 把计数器减到 0。
- 典型场景:启动等待(如微服务启动时等所有配置加载完成)、并行任务汇总(10 个线程抓网页,主线程等全部返回再解析)
- 关键限制:
CountDownLatch是**一次性**的——计数器归零后无法重置,再次await()会立即返回 - 务必配超时:
latch.await(5, TimeUnit.SECONDS),否则某个子线程崩溃未countDown(),主线程就永久卡死 - 异常处理要兜底:子线程内
countDown()必须放在finally块里,防止异常跳过导致计数器永远不归零
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> { try { fetchData(); } finally { latch.countDown(); } }).start();
// ... 启动另外两个
if (!latch.await(3, TimeUnit.SECONDS)) {
throw new RuntimeException("初始化超时");
}
Semaphore 不是锁,是“许可证发放器”
它不关心“谁在访问”,只控制“最多几个线程能同时进”。本质是资源配额管理,不是互斥控制。
- 适用场景:限流(如 API 每秒最多 100 调用)、连接池许可管理(DB 连接池最大 20,用
Semaphore(20)控制获取)、避免雪崩(下游扛不住时主动拒绝) - 注意 acquire() 可能被中断:
semaphore.acquire()抛InterruptedException,必须处理;更安全的是用tryAcquire(long timeout, ...) - 别漏掉
release():必须和acquire()成对出现,且最好也写在finally里,否则许可证永久丢失 - 公平性可选:
new Semaphore(5, true)表示按请求顺序分配,但性能略低,默认非公平
CyclicBarrier 的核心价值:多轮循环 + 屏障动作
它不是“等别人做完”,而是“大家约好一起走到这儿,齐步迈向下一段”。这才是它和 CountDownLatch 的本质区别。
立即学习“Java免费学习笔记(深入)”;
- 典型场景:分阶段计算(比如 8 个模型 worker 同步完成第 1 轮训练后,统一校验指标再进第 2 轮)、压测模拟并发请求(所有线程 wait 在 barrier,然后同时放开)
- 可重用:
reset()能重置计数器,支持无限轮次;而CountDownLatch不能 - 屏障动作(barrier action)很实用:构造时传入
Runnable,所有线程到达后、放行前执行一次(如日志打点、状态检查),失败会抛BrokenBarrierException并让所有线程退出 - 注意异常传播:
await()可能抛BrokenBarrierException(有人中断/超时/异常)或InterruptedException,二者都需捕获
别把 ConcurrentHashMap 当普通 Map 用
它不是“线程安全的 HashMap”,而是一个为高并发设计的、有明确行为边界的专用结构。
-
put()/get()安全,但size()在 JDK8 中是估算值(分段计数可能未完全合并),不要用于强一致性判断 -
key和value都禁止为 null,否则运行时报NullPointerException—— 这是刻意设计,避免在模糊语义(null 是没查到?还是存的就是 null?)上引发并发歧义 - 复合操作必须用原子方法:
computeIfAbsent()、merge()、replace()才真正线程安全;写成if (!map.containsKey(k)) map.put(k, v)就是竞态条件 - 迭代器是弱一致的:遍历时允许其他线程修改,不会抛
ConcurrentModificationException,但你可能看不到最新更新
真正难的不是记住这些类怎么写,而是每次写多线程逻辑前,先问自己一句:我到底是在协调进度(用 CountDownLatch/CyclicBarrier),还是在控制资源用量(用 Semaphore),还是在安全地读写共享数据(选对并发集合+原子操作)——选错工具,后面全是坑。










