一、四大金刚登场 🎬
快速识别表
| 工具类 | 一句话描述 | 生活比喻 |
|---|---|---|
| CountDownLatch | 等待N个事件完成 | 🎆 等所有烟花准备好再一起放 |
| CyclicBarrier | N个线程互相等待 | 🏃 跑步比赛,都到终点才开始下一轮 |
| Semaphore | 控制并发数量 | 🅿️ 停车场,只有5个车位 |
| Phaser | 多阶段协同 | 🎮 游戏关卡,都过关才进下一关 |
二、CountDownLatch - 倒计时门栓 🔒
核心思想
就像发射火箭🚀:
检查燃料 ✅
检查引擎 ✅
检查天气 ✅
倒计时:3... 2... 1... 🔥 发射!
所有检查完成(countDown到0),才能发射(await通过)
API
CountDownLatch latch = new CountDownLatch(3); // 计数器=3
// 线程1、2、3
latch.countDown(); // 计数器-1
// 主线程
latch.await(); // 阻塞,直到计数器=0
完整示例
public class RocketLaunch {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(3); // 3个检查项
// 检查1:燃料
new Thread(() -> {
System.out.println("检查燃料...");
sleep(1000);
System.out.println("✅ 燃料正常");
latch.countDown(); // 完成一项
}, "燃料检查").start();
// 检查2:引擎
new Thread(() -> {
System.out.println("检查引擎...");
sleep(2000);
System.out.println("✅ 引擎正常");
latch.countDown(); // 完成一项
}, "引擎检查").start();
// 检查3:天气
new Thread(() -> {
System.out.println("检查天气...");
sleep(1500);
System.out.println("✅ 天气良好");
latch.countDown(); // 完成一项
}, "天气检查").start();
System.out.println("等待所有检查完成...");
latch.await(); // 阻塞,等待计数器归零
System.out.println("🚀 所有检查完成,发射!");
}
}
输出:
检查燃料...
检查引擎...
检查天气...
等待所有检查完成...
✅ 燃料正常
✅ 天气良好
✅ 引擎正常
🚀 所有检查完成,发射!
典型应用场景
1️⃣ 并行计算,等待所有完成
public class ParallelCompute {
public static void main(String[] args) throws Exception {
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
long[] results = new long[threadCount];
// 10个线程并行计算
for (int i = 0; i < threadCount; i++) {
final int index = i;
new Thread(() -> {
// 计算这一段
results[index] = compute(index);
latch.countDown();
}).start();
}
// 等待所有线程完成
latch.await();
// 汇总结果
long total = Arrays.stream(results).sum();
System.out.println("总和:" + total);
}
}
2️⃣ 模拟并发测试
public class ConcurrentTest {
public static void main(String[] args) throws Exception {
int threadCount = 100;
CountDownLatch startLatch = new CountDownLatch(1); // 起跑枪
CountDownLatch endLatch = new CountDownLatch(threadCount); // 终点
// 100个线程准备
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
startLatch.await(); // 等待起跑枪
// 执行测试
testService.doSomething();
} finally {
endLatch.countDown(); // 到达终点
}
}).start();
}
System.out.println("所有线程就绪,开始!");
long start = System.currentTimeMillis();
startLatch.countDown(); // 🔫 鸣枪!所有线程同时开始
endLatch.await(); // 等待所有线程结束
long end = System.currentTimeMillis();
System.out.println("测试完成,耗时:" + (end - start) + "ms");
}
}
注意事项 ⚠️
// ❌ 错误:计数器不会重置
CountDownLatch latch = new CountDownLatch(3);
latch.countDown();
latch.countDown();
latch.countDown(); // 计数器=0
latch.await(); // 通过
// 再次使用?
latch.countDown(); // 💥 计数器已经是0,不会再减了
latch.await(); // 立即通过,不会等待
// ✅ 正确:一次性使用,不可重置
// 如果需要重复使用,用CyclicBarrier
三、CyclicBarrier - 循环栅栏 🚧
核心思想
就像团队旅游🚌:
10个人约好9点在景点门口集合
早到的人要等晚到的人
所有人都到了,才能一起进去
而且:
- 可以重复使用(去下一个景点)
- 可以设置集合后的动作(拍照📷)
API
CyclicBarrier barrier = new CyclicBarrier(
3, // 参与线程数
() -> { // 所有线程到达后的动作(可选)
System.out.println("🎉 所有人都到了,出发!");
}
);
// 每个线程
barrier.await(); // 阻塞,等待其他线程
完整示例
public class TourGroup {
public static void main(String[] args) {
int peopleCount = 5;
CyclicBarrier barrier = new CyclicBarrier(peopleCount, () -> {
System.out.println("✅ 所有人都到了!拍照留念 📷");
});
// 5个游客
for (int i = 1; i <= peopleCount; i++) {
final int id = i;
new Thread(() -> {
try {
// 第一个景点
System.out.println("游客" + id + "到达景点1");
barrier.await(); // 等待其他人
System.out.println("游客" + id + "开始游玩景点1");
Thread.sleep(1000); // 游玩中
// 第二个景点
System.out.println("游客" + id + "到达景点2");
barrier.await(); // 又等待其他人
System.out.println("游客" + id + "开始游玩景点2");
} catch (Exception e) {
e.printStackTrace();
}
}, "游客" + i).start();
}
}
}
输出:
游客1到达景点1
游客2到达景点1
游客3到达景点1
游客4到达景点1
游客5到达景点1
✅ 所有人都到了!拍照留念 📷
游客1开始游玩景点1
游客2开始游玩景点1
游客3开始游玩景点1
游客4开始游玩景点1
游客5开始游玩景点1
游客1到达景点2
游客2到达景点2
游客3到达景点2
游客4到达景点2
游客5到达景点2
✅ 所有人都到了!拍照留念 📷
游客1开始游玩景点2
...
典型应用场景
1️⃣ 多线程计算,分阶段
public class ParallelMatrix {
public static void main(String[] args) {
int threadCount = 4;
CyclicBarrier barrier = new CyclicBarrier(threadCount);
for (int i = 0; i < threadCount; i++) {
final int id = i;
new Thread(() -> {
// 阶段1:加载数据
loadData(id);
barrier.await(); // 等待所有线程加载完
// 阶段2:计算
compute(id);
barrier.await(); // 等待所有线程计算完
// 阶段3:写入结果
writeResult(id);
barrier.await(); // 等待所有线程写入完
}).start();
}
}
}
2️⃣ 多玩家游戏
public class MultiPlayerGame {
public static void main(String[] args) {
int playerCount = 4;
CyclicBarrier barrier = new CyclicBarrier(playerCount, () -> {
System.out.println("🎮 所有玩家就绪,游戏开始!");
});
for (int i = 1; i <= playerCount; i++) {
final int id = i;
new Thread(() -> {
try {
// 准备阶段
System.out.println("玩家" + id + "准备中...");
Thread.sleep(new Random().nextInt(3000));
System.out.println("玩家" + id + "准备完毕!");
barrier.await(); // 等待所有玩家
// 游戏开始
System.out.println("玩家" + id + "开始游戏");
} catch (Exception e) {
e.printStackTrace();
}
}, "玩家" + i).start();
}
}
}
CountDownLatch vs CyclicBarrier
| 对比项 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 计数方向 | 倒数(N→0) | 正数(0→N) |
| 可重用 | ❌ 一次性 | ✅ 可重用 |
| 等待方 | 一个或多个线程等待 | 所有线程互相等待 |
| 动作触发 | 不支持 | 支持(barrierAction) |
| 角色 | 主从关系 | 对等关系 |
| 使用场景 | 主线程等待工作线程 | 多线程协同工作 |
生活比喻对比:
CountDownLatch:
领导(主线程)等3个员工(工作线程)完成任务
员工完成后各回各家,不互相等待
CyclicBarrier:
3个朋友约好一起去玩
都到了才一起出发
可以玩多个景点(可重用)
四、Semaphore - 信号量 🚦
核心思想
停车场🅿️:
总共5个车位
现在停了3辆车,还剩2个
第6辆车来了,没车位,等待
有车离开,释放车位,第6辆可以进
信号量 = 可用资源数量
API
Semaphore semaphore = new Semaphore(5); // 5个许可
// 获取许可
semaphore.acquire(); // 阻塞获取,直到有许可
semaphore.tryAcquire(); // 尝试获取,立即返回true/false
semaphore.tryAcquire(3, TimeUnit.SECONDS); // 超时获取
// 释放许可
semaphore.release();
// 查询
semaphore.availablePermits(); // 可用许可数
完整示例
public class ParkingLot {
private static final Semaphore PARKING = new Semaphore(5); // 5个车位
public static void main(String[] args) {
// 10辆车
for (int i = 1; i <= 10; i++) {
final int carId = i;
new Thread(() -> {
try {
System.out.println("🚗 车" + carId + "到达停车场,等待车位...");
PARKING.acquire(); // 获取车位
System.out.println("✅ 车" + carId + "停进车位(剩余车位:" +
PARKING.availablePermits() + ")");
// 停车
Thread.sleep(new Random().nextInt(5000));
System.out.println("🚗 车" + carId + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
PARKING.release(); // 释放车位
System.out.println("✅ 车位释放(剩余车位:" +
PARKING.availablePermits() + ")");
}
}, "车" + i).start();
}
}
}
输出:
🚗 车1到达停车场,等待车位...
✅ 车1停进车位(剩余车位:4)
🚗 车2到达停车场,等待车位...
✅ 车2停进车位(剩余车位:3)
🚗 车3到达停车场,等待车位...
✅ 车3停进车位(剩余车位:2)
🚗 车4到达停车场,等待车位...
✅ 车4停进车位(剩余车位:1)
🚗 车5到达停车场,等待车位...
✅ 车5停进车位(剩余车位:0)
🚗 车6到达停车场,等待车位... ← 等待中
🚗 车7到达停车场,等待车位... ← 等待中
...
🚗 车1离开车位
✅ 车位释放(剩余车位:1)
✅ 车6停进车位(剩余车位:0) ← 车6进来了
...
典型应用场景
1️⃣ 限流
public class RateLimiter {
// 限制同时只有10个请求
private static final Semaphore LIMITER = new Semaphore(10);
public void handleRequest(String request) {
try {
if (LIMITER.tryAcquire(100, TimeUnit.MILLISECONDS)) {
try {
// 处理请求
processRequest(request);
} finally {
LIMITER.release();
}
} else {
// 限流,拒绝请求
throw new TooManyRequestsException();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2️⃣ 连接池
public class ConnectionPool {
private final Semaphore semaphore;
private final Queue<Connection> connections;
public ConnectionPool(int size) {
this.semaphore = new Semaphore(size);
this.connections = new ConcurrentLinkedQueue<>();
// 初始化连接
for (int i = 0; i < size; i++) {
connections.offer(createConnection());
}
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // 获取许可
Connection conn = connections.poll();
return conn != null ? conn : createConnection();
}
public void releaseConnection(Connection conn) {
connections.offer(conn);
semaphore.release(); // 释放许可
}
}
3️⃣ 批量任务限流
public class BatchProcessor {
// 最多5个线程同时处理
private static final Semaphore SEMAPHORE = new Semaphore(5);
public void processBatch(List<Task> tasks) {
tasks.forEach(task -> {
new Thread(() -> {
try {
SEMAPHORE.acquire();
try {
processTask(task);
} finally {
SEMAPHORE.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
});
}
}
公平模式 vs 非公平模式
// 非公平模式(默认,性能高)
Semaphore unfair = new Semaphore(5, false);
// 新来的线程可能插队
// 公平模式(先来先得)
Semaphore fair = new Semaphore(5, true);
// 严格按照请求顺序分配许可
五、Phaser - 多阶段栅栏 🎮
核心思想
就像游戏闯关🎮:
第1关:打小怪
- 所有玩家都打完 → 进入第2关
第2关:打BOSS
- 所有玩家都打完 → 进入第3关
第3关:收集宝藏
- 所有玩家都收集完 → 游戏结束
特点:
- 多阶段(Phase)
- 动态调整参与者数量
- 可以中途加入/退出
API
Phaser phaser = new Phaser(3); // 3个参与者
// 线程到达并等待
phaser.arriveAndAwaitAdvance(); // 到达当前阶段,等待进入下一阶段
// 到达但不等待
phaser.arrive();
// 注册新参与者
phaser.register();
// 注销参与者
phaser.arriveAndDeregister();
// 获取当前阶段
phaser.getPhase();
完整示例
public class GameLevel {
public static void main(String[] args) {
int playerCount = 3;
Phaser phaser = new Phaser(playerCount) {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("===== 第" + (phase + 1) + "关完成 =====\n");
return phase >= 2; // 3关后结束(phase从0开始)
}
};
// 3个玩家
for (int i = 1; i <= playerCount; i++) {
final int id = i;
new Thread(() -> {
// 第1关
System.out.println("玩家" + id + "开始第1关");
sleep(new Random().nextInt(3000));
System.out.println("玩家" + id + "完成第1关");
phaser.arriveAndAwaitAdvance();
// 第2关
System.out.println("玩家" + id + "开始第2关");
sleep(new Random().nextInt(3000));
System.out.println("玩家" + id + "完成第2关");
phaser.arriveAndAwaitAdvance();
// 第3关
System.out.println("玩家" + id + "开始第3关");
sleep(new Random().nextInt(3000));
System.out.println("玩家" + id + "完成第3关");
phaser.arriveAndAwaitAdvance();
System.out.println("🎉 玩家" + id + "通关!");
}, "玩家" + i).start();
}
}
}
输出:
玩家1开始第1关
玩家2开始第1关
玩家3开始第1关
玩家2完成第1关
玩家1完成第1关
玩家3完成第1关
===== 第1关完成 =====
玩家1开始第2关
玩家2开始第2关
玩家3开始第2关
玩家3完成第2关
玩家1完成第2关
玩家2完成第2关
===== 第2关完成 =====
玩家1开始第3关
玩家2开始第3关
玩家3开始第3关
玩家2完成第3关
玩家3完成第3关
玩家1完成第3关
===== 第3关完成 =====
🎉 玩家1通关!
🎉 玩家2通关!
🎉 玩家3通关!
动态参与者
public class DynamicPhaser {
public static void main(String[] args) throws Exception {
Phaser phaser = new Phaser(1); // 初始1个参与者(主线程)
// 初始3个任务
for (int i = 1; i <= 3; i++) {
phaser.register(); // 注册参与者
startTask(i, phaser);
}
// 等待第1阶段
phaser.arriveAndAwaitAdvance();
System.out.println("第1阶段完成\n");
// 动态添加2个任务
for (int i = 4; i <= 5; i++) {
phaser.register();
startTask(i, phaser);
}
// 等待第2阶段
phaser.arriveAndAwaitAdvance();
System.out.println("第2阶段完成\n");
phaser.arriveAndDeregister(); // 主线程退出
}
private static void startTask(int id, Phaser phaser) {
new Thread(() -> {
System.out.println("任务" + id + "开始");
sleep(1000);
System.out.println("任务" + id + "完成");
phaser.arriveAndDeregister(); // 完成并退出
}).start();
}
}
CyclicBarrier vs Phaser
| 对比项 | CyclicBarrier | Phaser |
|---|---|---|
| 阶段数 | 单阶段(可循环) | 多阶段 |
| 参与者 | 固定数量 | 动态增减 ✨ |
| 灵活性 | 中等 | 高 ✨ |
| 复杂度 | 简单 | 复杂 |
| 性能 | 较高 | 较低 |
| 适用场景 | 简单循环同步 | 复杂多阶段协调 |
六、四大工具类终极对比 ⚔️
对比表
| 特性 | CountDownLatch | CyclicBarrier | Semaphore | Phaser |
|---|---|---|---|---|
| 作用 | 等待事件完成 | 线程互相等待 | 限制并发数 | 多阶段协调 |
| 计数器 | 递减(N→0) | 递增(0→N) | 可增可减 | 阶段递增 |
| 可重用 | ❌ | ✅ | ✅ | ✅ |
| 参与者 | 固定 | 固定 | 动态 | 动态 ✨ |
| 等待方 | 主线程等工作线程 | 线程互等 | 不等待 | 线程互等 |
| 典型场景 | 并行计算 | 多线程协同 | 限流 | 游戏关卡 |
选择决策树
需要同步吗?
│
├─ No → 不需要这些工具
│
└─ Yes
│
├─ 限制并发数? → Semaphore
│
├─ 多个阶段?
│ ├─ Yes
│ │ ├─ 参与者动态变化? → Phaser
│ │ └─ 参与者固定? → CyclicBarrier
│ │
│ └─ No(单阶段)
│ ├─ 需要重复使用? → CyclicBarrier
│ └─ 一次性使用? → CountDownLatch
七、实战案例 🔬
案例1:并行数据导入
// 需求:导入100万条数据,10个线程并行,主线程等待完成
public class DataImporter {
public void importData(List<Data> allData) throws Exception {
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
int chunkSize = allData.size() / threadCount;
for (int i = 0; i < threadCount; i++) {
int start = i * chunkSize;
int end = (i == threadCount - 1) ? allData.size() : (i + 1) * chunkSize;
List<Data> chunk = allData.subList(start, end);
new Thread(() -> {
try {
// 导入这一批数据
batchInsert(chunk);
} finally {
latch.countDown();
}
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("导入完成!");
}
}
// 为什么用CountDownLatch?
// ✅ 主线程等待工作线程
// ✅ 一次性使用
// ✅ 简单明了
案例2:多线程计算,分阶段
// 需求:矩阵运算,分3个阶段:加载→计算→保存
public class MatrixCompute {
public void compute() {
int threadCount = 4;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("阶段完成");
});
for (int i = 0; i < threadCount; i++) {
final int id = i;
new Thread(() -> {
// 阶段1:加载数据
loadMatrix(id);
barrier.await();
// 阶段2:计算
computeMatrix(id);
barrier.await();
// 阶段3:保存结果
saveMatrix(id);
barrier.await();
}).start();
}
}
}
// 为什么用CyclicBarrier?
// ✅ 多个阶段
// ✅ 线程互相等待
// ✅ 可重用(3个阶段)
案例3:API限流
// 需求:限制同时最多100个请求
@Service
public class ApiService {
private static final Semaphore LIMITER = new Semaphore(100);
public Response handleRequest(Request request) {
try {
if (!LIMITER.tryAcquire(100, TimeUnit.MILLISECONDS)) {
return Response.error("系统繁忙");
}
try {
// 处理请求
return processRequest(request);
} finally {
LIMITER.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Response.error("请求中断");
}
}
}
// 为什么用Semaphore?
// ✅ 限制并发数
// ✅ 超时控制(tryAcquire)
案例4:多阶段测试
// 需求:性能测试,分5轮,每轮增加压力
public class PerformanceTest {
public void test() {
Phaser phaser = new Phaser(1) { // 主线程
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("第" + (phase + 1) + "轮完成");
return phase >= 4; // 5轮后结束
}
};
for (int round = 1; round <= 5; round++) {
final int concurrency = round * 100; // 并发数递增
// 注册参与者
for (int i = 0; i < concurrency; i++) {
phaser.register();
}
// 启动线程
for (int i = 0; i < concurrency; i++) {
new Thread(() -> {
sendRequest();
phaser.arriveAndDeregister();
}).start();
}
phaser.arriveAndAwaitAdvance(); // 等待本轮完成
}
phaser.arriveAndDeregister(); // 主线程退出
}
}
// 为什么用Phaser?
// ✅ 多阶段(5轮)
// ✅ 每轮参与者数量不同(动态)
// ✅ 灵活控制
八、面试应答模板 🎤
面试官:说说CountDownLatch、CyclicBarrier、Semaphore的区别?
你的回答:
这三个都是并发工具类,但作用不同:
CountDownLatch - 倒计时门栓:
- 作用:一个或多个线程等待N个事件完成
- 计数器:从N递减到0
- 可重用:❌ 一次性
- 场景:主线程等待工作线程完成,比如并行计算、批量导入
- 例子:火箭发射,等待所有检查项完成
CyclicBarrier - 循环栅栏:
- 作用:N个线程互相等待,都到达才继续
- 计数器:从0递增到N
- 可重用:✅ 可循环使用
- 场景:多线程协同工作,分阶段执行
- 例子:旅游团队,所有人到齐才出发
Semaphore - 信号量:
- 作用:限制同时访问资源的线程数量
- 计数器:可增可减
- 可重用:✅
- 场景:限流、连接池、停车场
- 例子:停车场,只有5个车位
核心区别:
- CountDownLatch是主从关系(主线程等工作线程)
- CyclicBarrier是对等关系(线程互相等待)
- Semaphore是资源控制(限制并发数)
Phaser:
- 是CyclicBarrier的增强版
- 支持多阶段、动态参与者
- 适合复杂场景,但更复杂
选择建议:
- 简单等待 → CountDownLatch
- 多阶段协同 → CyclicBarrier
- 限流 → Semaphore
- 复杂多阶段+动态参与 → Phaser
九、总结 🎯
四大并发工具类速记:
CountDownLatch 🎆
├─ 等N个事件
├─ 倒计时(N→0)
├─ 一次性
└─ 主线程等工作线程
CyclicBarrier 🚧
├─ N个线程互等
├─ 计数(0→N)
├─ 可重用
└─ 线程对等协作
Semaphore 🚦
├─ 限并发数
├─ 许可证管理
├─ acquire/release
└─ 限流、资源池
Phaser 🎮
├─ 多阶段
├─ 动态参与者
├─ 最灵活
└─ 复杂场景
记忆口诀:
门栓等完成,
栅栏互等待,
信号控数量,
阶段用Phaser!🎵
核心要点:
- ✅ CountDownLatch:一次性,主等工
- ✅ CyclicBarrier:可重用,线程互等
- ✅ Semaphore:限流,许可证
- ✅ Phaser:多阶段,动态参与
恭喜你!31-40个知识点全部完成!🎉