📋 概述
一句话:多个线程怎么"等齐了再走"?JUC 同步器提供 5 种现成方案,底层全基于 AQS 框架,解决不同的线程协作问题。
为什么需要同步器?
假设你有一个批量任务:主线程启动 5 个子任务,等它们全部完成后再汇总结果。如果用 Thread.join(),代码会非常笨重。更复杂的需求——比如"5 个线程分阶段计算,每阶段全部到齐才进入下一阶段"——手写几乎不可能。
JDK 提供的同步器就是解决这类问题的轮子:
| 同步器 | 解决什么问题 | 一句话 |
|---|---|---|
| CountDownLatch | 等 N 个事件完成 | 接力赛最后一棒 |
| CyclicBarrier | N 个线程互相等齐 | 栅栏闸机 |
| Semaphore | 控制并发数 | 停车场限流 |
| Phaser | 多阶段动态协调 | 多阶段接力赛 |
| Exchanger | 两线程交换数据 | 交换礼物 |
💡 生活类比
5 种同步器适用场景
类比详解
| 同步器 | 生活场景 | 核心机制 |
|---|---|---|
| CountDownLatch | 接力赛最后一棒:等所有人跑完才庆祝 | 计数减到 0,唤醒等待者 |
| CyclicBarrier | 栅栏:等所有人到齐才能过闸 | 计数加到 parties,一起通过 |
| Semaphore | 停车场:只有 N 个车位,满了进不去 | 许可数控制并发 |
| Phaser | 多阶段接力赛:比 CyclicBarrier 更灵活 | 多阶段 + 动态线程 |
| Exchanger | 两人交换礼物:两两交换数据 | 配对交换 |
🔍 CountDownLatch(倒计时门栓)
原理剖析
CountDownLatch 使用 AQS 的共享模式,state 初始值为 count:
核心要点:
state初始值 = count(倒计数)countDown()每次 CAS 减 1await()阻塞,直到state == 0唤醒所有等待线程- 一次性:count=0 后不可重置
Demo 1:主线程等子任务完成
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 3; i++) {
final int taskId = i;
executor.submit(() -> {
try {
System.out.println("任务" + taskId + "开始执行");
Thread.sleep(1000);
System.out.println("任务" + taskId + "执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
System.out.println("主线程等待子任务完成...");
latch.await();
System.out.println("所有子任务完成,主线程继续");
executor.shutdown();
}
}
输出:
任务1开始执行
任务2开始执行
任务3开始执行
主线程等待子任务完成...
任务2执行完成
任务1执行完成
任务3执行完成
所有子任务完成,主线程继续
Demo 2:多线程汇总
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger total = new AtomicInteger(0);
for (int i = 1; i <= threadCount; i++) {
final int value = i;
new Thread(() -> {
try {
int result = value * 10;
total.addAndGet(result);
System.out.println("线程" + value + "计算: " + result);
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("汇总结果: " + total.get());
}
}
输出:
线程1计算: 10
线程3计算: 30
线程2计算: 20
线程4计算: 40
线程5计算: 50
汇总结果: 150
🔍 CyclicBarrier(循环屏障)
原理剖析
CyclicBarrier 使用 ReentrantLock + Condition 实现,parties 计数器记录等待线程数:
核心要点:
parties初始值为 N,count每次 await 减 1count == 0时,执行barrierAction(可选),然后唤醒所有线程- 可循环使用:屏障触发后自动重置
- 支持
reset()手动重置
Demo 1:多线程分阶段计算
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierDemo1 {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () ->
System.out.println("--- 阶段屏障触发,进入下一阶段 ---")
);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 1; i <= threadCount; i++) {
final int threadId = i;
executor.submit(() -> {
try {
for (int phase = 1; phase <= 3; phase++) {
System.out.println("线程" + threadId + " 阶段" + phase + "计算中...");
Thread.sleep(500);
System.out.println("线程" + threadId + " 阶段" + phase + "完成,等待其他线程");
barrier.await(); // 等待所有线程
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
输出:
线程1 阶段1计算中...
线程2 阶段1计算中...
线程3 阶段1计算中...
线程1 阶段1完成,等待其他线程
线程3 阶段1完成,等待其他线程
线程2 阶段1完成,等待其他线程
--- 阶段屏障触发,进入下一阶段 ---
线程2 阶段2计算中...
线程1 阶段2计算中...
线程3 阶段2计算中...
...
Demo 2:barrierAction 汇总
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
public class CyclicBarrierDemo2 {
public static void main(String[] args) {
AtomicInteger phaseResult = new AtomicInteger(0);
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("当前阶段汇总: " + phaseResult.get());
phaseResult.set(0); // 重置,为下一阶段准备
});
for (int i = 1; i <= 3; i++) {
final int value = i;
new Thread(() -> {
try {
for (int phase = 1; phase <= 2; phase++) {
int result = value * phase;
phaseResult.addAndGet(result);
System.out.println("线程" + value + " 阶段" + phase + " 贡献: " + result);
barrier.await();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
🔍 Semaphore(信号量)
原理剖析
Semaphore 使用 AQS 的共享模式,state 表示剩余许可数:
核心要点:
state= permits(许可数)acquire()获取许可(state-1),为 0 时阻塞release()释放许可(state+1),唤醒等待线程- 支持公平/非公平模式(默认非公平)
Demo 1:停车场限流
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SemaphoreDemo1 {
public static void main(String[] args) {
int parkingSpaces = 3;
Semaphore semaphore = new Semaphore(parkingSpaces);
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 5; i++) {
final int carId = i;
executor.submit(() -> {
try {
System.out.println("车辆" + carId + "正在寻找车位...");
semaphore.acquire();
System.out.println("车辆" + carId + "找到车位,开始停车");
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("车辆" + carId + "离开停车场");
semaphore.release();
}
});
}
executor.shutdown();
}
}
输出:
车辆1正在寻找车位...
车辆2正在寻找车位...
车辆3正在寻找车位...
车辆4正在寻找车位...
车辆5正在寻找车位...
车辆1找到车位,开始停车
车辆2找到车位,开始停车
车辆3找到车位,开始停车
车辆4找到车位,开始停车
车辆1离开停车场
车辆5找到车位,开始停车
...
Demo 2:数据库连接池限流
import java.util.concurrent.Semaphore;
import java.util.concurrent.ArrayBlockingQueue;
public class SemaphoreDemo2 {
static class ConnectionPool {
private final Semaphore semaphore;
private final ArrayBlockingQueue<String> pool;
public ConnectionPool(int poolSize) {
this.semaphore = new Semaphore(poolSize);
this.pool = new ArrayBlockingQueue<>(poolSize);
for (int i = 0; i < poolSize; i++) {
pool.offer("Connection-" + (i + 1));
}
}
public String getConnection() throws InterruptedException {
semaphore.acquire();
String conn = pool.poll();
System.out.println("获取连接: " + conn + ",剩余许可: " + semaphore.availablePermits());
return conn;
}
public void releaseConnection(String conn) {
pool.offer(conn);
semaphore.release();
System.out.println("释放连接: " + conn + ",剩余许可: " + semaphore.availablePermits());
}
}
public static void main(String[] args) {
ConnectionPool pool = new ConnectionPool(3);
for (int i = 1; i <= 5; i++) {
final int threadId = i;
new Thread(() -> {
try {
String conn = pool.getConnection();
Thread.sleep(1000);
pool.releaseConnection(conn);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
🔍 Phaser(阶段协调器)
原理剖析
Phaser 是 CyclicBarrier 的升级版,支持:
- 动态调整参与线程数(register/deregister)
- 多阶段协调(phase 0 → 1 → 2)
- 分层Phaser(树形结构)
核心 API:
register():注册新线程arrive():到达(不等待)arriveAndAwaitAdvance():到达并等待arriveAndDeregister():到达并注销onAdvance(int phase, int registeredParties):阶段推进钩子
Demo:3阶段任务
import java.util.concurrent.Phaser;
public class PhaserDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(1); // 主线程注册
// 启动3个worker
for (int i = 1; i <= 3; i++) {
phaser.register();
final int workerId = i;
new Thread(() -> {
try {
for (int phase = 0; phase < 3; phase++) {
System.out.println("Worker" + workerId + " 阶段" + phase + "开始");
Thread.sleep(500);
System.out.println("Worker" + workerId + " 阶段" + phase + "完成");
phaser.arriveAndAwaitAdvance(); // 等待所有线程
}
} finally {
phaser.arriveAndDeregister();
}
}).start();
}
// 主线程参与3个阶段
for (int phase = 0; phase < 3; phase++) {
phaser.arriveAndAwaitAdvance();
System.out.println("主线程: 阶段" + phase + "全部完成");
}
phaser.arriveAndDeregister();
System.out.println("所有阶段完成");
}
}
🔍 Exchanger(交换器)
原理剖析
Exchanger 用于两个线程之间交换数据,基于 CAS + volatile 实现。
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
try {
String data1 = "来自线程A的数据";
System.out.println("线程A准备交换: " + data1);
String data2 = exchanger.exchange(data1);
System.out.println("线程A收到: " + data2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.submit(() -> {
try {
String data1 = "来自线程B的数据";
System.out.println("线程B准备交换: " + data1);
String data2 = exchanger.exchange(data1);
System.out.println("线程B收到: " + data2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.shutdown();
}
}
输出:
线程A准备交换: 来自线程A的数据
线程B准备交换: 来自线程B的数据
线程A收到: 来自线程B的数据
线程B收到: 来自线程A的数据
📊 5种同步器对比表
| 特性 | CountDownLatch | CyclicBarrier | Semaphore | Phaser | Exchanger |
|---|---|---|---|---|---|
| 实现原理 | AQS 共享模式 | ReentrantLock + Condition | AQS 共享模式 | CAS + volatile | CAS + volatile |
| 是否可重用 | ❌ 一次性 | ✅ 可循环 | ✅ 可重复使用 | ✅ 多阶段 | ✅ 可重复使用 |
| 计数方向 | 减到 0 | 加到 parties | permits 递减 | phase 递增 | N/A |
| 等待方式 | N 等 1 或 1 等 N | N 等 N | N 等许可 | N 等阶段推进 | 2 等 2 |
| 回调支持 | ❌ | ✅ barrierAction | ❌ | ✅ onAdvance | ❌ |
| 动态线程 | ❌ | ❌ | ✅ | ✅ register/deregister | ❌ |
| 分层 | ❌ | ❌ | ❌ | ✅ 树形结构 | ❌ |
| 公平性 | N/A | N/A | 可选 | N/A | N/A |
| 性能 | 高 | 中 | 高 | 中 | 高 |
| 适用场景 | 主从协调、压测 | 多阶段任务、数据汇总 | 限流、资源池 | 复杂多阶段协调 | 数据交换、校对 |
⚠️ 常见问题与踩坑
Q1: CountDownLatch 和 CyclicBarrier 区别?
| 维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 计数方向 | 减到 0 | 加到 parties |
| 等待方式 | N 等 1 或 1 等 N | N 等 N |
| 复用 | ❌ 一次性 | ✅ 可循环 |
| 回调 | ❌ | ✅ barrierAction |
| 基于 | AQS 共享模式 | ReentrantLock + Condition |
| 场景 | 主线程等子任务 | 多线程互相等齐 |
Q2: CountDownLatch 可以重复使用吗?
❌ 不可以。CountDownLatch 是一次性的,count=0 后无法重置。如果需要重复使用,用 CyclicBarrier 或创建新的 CountDownLatch。
Q3: Semaphore 的公平性怎么选?
- 默认非公平:性能高 3-5 倍,但可能饥饿
- 公平模式:
new Semaphore(n, true),严格 FIFO,吞吐低 - 选型:限流用非公平,避免饥饿用公平
Q4: Phaser vs CyclicBarrier?
| 维度 | CyclicBarrier | Phaser |
|---|---|---|
| 线程数 | 固定 | 动态(register/deregister) |
| 阶段 | 单阶段循环 | 多阶段 |
| 分层 | ❌ | ✅ 树形 |
| 钩子 | barrierAction | onAdvance(phase, parties) |
结论:简单同步用 CyclicBarrier,复杂场景用 Phaser。
Q5: 什么时候用 Exchanger?
- 生产者-消费者双缓冲
- 遗传算法中交叉操作
- 校对工作:两个线程各自处理一半数据,最后交换校对
- 管线设计:前一个线程输出作为后一个输入
Q6: 同步器底层都用 AQS 吗?
| 同步器 | 是否用 AQS | 说明 |
|---|---|---|
| CountDownLatch | ✅ 共享模式 | state = count |
| CyclicBarrier | ❌ | ReentrantLock + Condition |
| Semaphore | ✅ 共享模式 | state = permits |
| Phaser | ❌ | CAS + volatile |
| Exchanger | ❌ | CAS + volatile |
🎯 最佳实践
选型决策表
| 场景 | 推荐同步器 | 理由 |
|---|---|---|
| 主线程等 N 个子任务完成 | CountDownLatch | 简单直接,一次性语义 |
| 多线程分阶段计算 | CyclicBarrier / Phaser | 可循环或多阶段 |
| 限流/资源池 | Semaphore | 控制并发数 |
| 复杂多阶段+动态线程 | Phaser | 灵活度最高 |
| 两线程数据交换 | Exchanger | 配对交换专用 |
| 等待服务启动 | CountDownLatch | 主从协调经典场景 |
| 并行压测 | CountDownLatch | 起始门+结束门 |
注意事项
- CountDownLatch 不可复用:需要重复等待,创建新实例或用 CyclicBarrier
- Semaphore release 必须 finally:避免许可泄露
- CyclicBarrier 异常处理:一个线程异常会导致其他线程永久等待
- Phaser 动态注册:确保所有线程都已注册,否则可能提前推进
- Exchanger 必须成对:单线程 exchange 会永久阻塞
💡 面试要点
- CountDownLatch 是 AQS 共享模式:state 初始值=count,countDown() CAS 减 1,await() 等 state=0 唤醒
- CyclicBarrier 用 ReentrantLock + Condition:不是 AQS,支持循环和 barrierAction
- Semaphore 限流利器:state=permits,acquire/release,支持公平/非公平
- Phaser 比 CyclicBarrier 更灵活:支持动态线程、多阶段、分层
- Exchanger 两两交换:基于 CAS,用于双缓冲、遗传算法等场景
- CountDownLatch vs CyclicBarrier 高频考点:一个减到 0,一个加到 parties;一个一次性,一个可循环
- 同步器底层都是 AQS:理解 state + CLH 队列 + CAS,同步器就是换一种 tryAcquire 实现
📝 总结
| 同步器 | 核心机制 | 适用场景 |
|---|---|---|
| CountDownLatch | AQS 共享,state 减到 0 | 主线程等子任务 |
| CyclicBarrier | ReentrantLock,N 等 N | 多阶段分段计算 |
| Semaphore | AQS 共享,permits 控制 | 限流、资源池 |
| Phaser | 多阶段动态协调 | 复杂多阶段任务 |
| Exchanger | CAS 配对交换 | 数据交换、校对 |
一句话:同步器是 AQS 的应用层,掌握 AQS 原理,这 5 个工具就是"换一种状态语义"而已。