并发工具类
java.util.concurrent 提供了很多线程协作工具。它们比直接写 wait/notify 更安全、更清晰,适合表达等待、屏障、限流、数据交换等常见并发模式。
# 1. CountDownLatch
CountDownLatch 让一个或多个线程等待计数归零。
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
executor.execute(() -> {
try {
init();
} finally {
latch.countDown();
}
});
}
latch.await();
start();
模型:
count = 3
线程 A 完成 -> count = 2
线程 B 完成 -> count = 1
线程 C 完成 -> count = 0
等待线程继续执行
CountDownLatch 一次性使用,计数归零后不能重置。
# 2. CyclicBarrier
CyclicBarrier 让一组线程互相等待,直到全部到达屏障。
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障");
});
每个线程:
doStepOne();
barrier.await();
doStepTwo();
模型:
线程 A 到达 barrier,等待
线程 B 到达 barrier,等待
线程 C 到达 barrier
│
▼
屏障打开,三个线程继续
它可以重复使用,因此叫 Cyclic。
# 3. CountDownLatch 与 CyclicBarrier
| 对比项 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 作用 | 一个或多个线程等待其他任务完成 | 多个线程互相等待 |
| 是否可复用 | 不可复用 | 可复用 |
| 计数方式 | countDown 减计数 | await 到达屏障 |
| 典型场景 | 主线程等待多个初始化任务 | 多阶段并行任务 |
# 4. Semaphore
Semaphore 用许可数控制同时访问资源的线程数量。
Semaphore semaphore = new Semaphore(10);
semaphore.acquire();
try {
callRemote();
} finally {
semaphore.release();
}
模型:
10 个许可
线程进入 -> acquire 消耗许可
线程离开 -> release 归还许可
许可为 0 -> 后续线程等待
适合限流:
- 同时访问某接口最多 N 个线程。
- 控制昂贵资源并发度。
- 防止下游被打爆。
# 5. Exchanger
Exchanger 用于两个线程交换数据。
Exchanger<String> exchanger = new Exchanger<>();
String received = exchanger.exchange("from A");
模型:
线程 A 携带 dataA 到达
线程 B 携带 dataB 到达
│
▼
A 得到 dataB
B 得到 dataA
使用场景较少,适合双线程数据交换。
# 6. Phaser
Phaser 是更灵活的阶段同步器,可动态注册参与者。
Phaser phaser = new Phaser(3);
phaser.arriveAndAwaitAdvance();
适合:
- 多阶段任务。
- 参与线程数量动态变化。
- 比
CyclicBarrier更复杂的同步流程。
# 7. 工具类选型
| 需求 | 工具 |
|---|---|
| 等待多个任务完成 | CountDownLatch |
| 多个线程分阶段一起推进 | CyclicBarrier |
| 限制并发数量 | Semaphore |
| 两个线程交换数据 | Exchanger |
| 多阶段动态参与者 | Phaser |
| 生产消费 | BlockingQueue |
# 8. 超时与异常
等待类方法通常支持超时:
boolean ok = latch.await(3, TimeUnit.SECONDS);
if (!ok) {
throw new TimeoutException("等待初始化超时");
}
不要无限等待外部条件,否则故障时线程可能永久挂住。
# 专家实践与边界
并发工具类的价值是把常见协作模式封装起来,减少手写 wait/notify 的错误。选型时先判断协作关系:是等待多个任务完成、让多个线程同时起跑、限制并发量,还是线程间交换数据。
一次性等待完成 -> CountDownLatch
多轮阶段同步 -> CyclicBarrier / Phaser
限制资源并发 -> Semaphore
线程间交换数据 -> Exchanger
| 工具 | 生命周期 | 典型场景 | 注意点 |
|---|---|---|---|
CountDownLatch | 一次性 | 主线程等待子任务 | 不能重置 |
CyclicBarrier | 可复用 | 多线程阶段对齐 | 屏障动作异常会破坏屏障 |
Semaphore | 长期 | 限流、连接许可 | 许可释放必须在 finally |
Phaser | 多阶段 | 动态参与方 | API 更复杂 |
线上问题常来自“异常路径没有释放许可”或“某个任务失败导致其他线程永远等”。因此等待必须有超时,释放必须放在 finally,任务失败要能传播。
# Tips 快问快答
Q:CountDownLatch 能重复使用吗? A:不能,计数归零后不能重置。
Q:CyclicBarrier 和 CountDownLatch 最大区别是什么? A:CyclicBarrier 是线程互相等待且可复用,CountDownLatch 是等待计数归零且一次性。
Q:Semaphore 适合做什么? A:限制同时访问某资源的线程数量。
Q:并发工具类能替代锁吗? A:它们解决协作问题,不是所有互斥问题的替代品。
Q:await 要不要设置超时? A:生产代码通常应该设置超时,避免永久等待。
Q:CyclicBarrier 中某个线程异常怎么办? A:屏障可能 broken,其他等待线程会收到异常。
Q:Phaser 适合什么场景? A:多阶段且参与者数量可能动态变化的任务。
Q:生产消费推荐哪个工具?
A:推荐 BlockingQueue。