JUC 的“同步器”解决的不是“互斥”(那是锁更擅长),而是 协作:让一组线程在某个时刻 一起开始 / 一起结束 / 分阶段推进 / 限流拿资源 / 成对交换数据

如果你已经理解了 AQS(state + CLH 队列 + park/unpark),那同步器可以看成:

  • 独占模式:常见于 Lock(互斥)。
  • 共享模式:常见于门闩/信号量(多个线程可同时“通过”)。

1. 选型速查(面试 + 工程)

目标 首选同步器 关键点 常见坑
等所有子任务完成再继续 CountDownLatch 一次性;countDown() 递减到 0 后释放所有 await() 忘记 finally countDown() 导致永远等待
N 个线程相互等待后一起继续 CyclicBarrier 可复用;到达 parties 后触发 barrierAction 任一线程中断/超时会 BrokenBarrier
控制并发量/资源池许可证 Semaphore permit 计数;公平/非公平 acquire() 后异常未 release() → permit 泄漏
多阶段栅栏(阶段可动态变更) Phaser 支持 register/deregister;多 phase 推进 注册数管理错误导致永远推进不了
两线程配对交换数据 Exchanger 交换点 rendezvous;支持超时 线程数不成对/超时处理不当

2. CountDownLatch:一次性的“倒计时门闩”

2.1 语义

  • 初始化一个计数 count
  • 线程调用 await():当 count != 0 就等待;当 count == 0 立刻通过。
  • 线程调用 countDown()count--,直到减到 0 时唤醒所有等待者。

典型场景:

  • 并行初始化(加载配置、预热缓存、建立连接)结束后再对外提供服务。
  • 批量并行任务聚合(fan-out / fan-in)。

2.2 代码模板(推荐写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ExecutorService pool = Executors.newFixedThreadPool(8);
CountDownLatch latch = new CountDownLatch(tasks.size());

for (Runnable task : tasks) {
pool.execute(() -> {
try {
task.run();
} finally {
// 关键:保证一定能走到
latch.countDown();
}
});
}

boolean ok = latch.await(3, TimeUnit.SECONDS);
if (!ok) {
// 超时:记录未完成项、降级、报警等
}

2.3 面试高频

  • CountDownLatch 不可复用:计数到 0 后不能重置;需要复用用 CyclicBarrier / Phaser
  • await() 响应中断:被中断会抛 InterruptedException,注意按规范 Thread.currentThread().interrupt() 回填标志。

3. CyclicBarrier:可复用的“集合点”

3.1 语义

  • 设置 parties:需要到齐的线程数。
  • 每个线程调用 await():到齐后 同时放行(并可执行一个 barrierAction)。
  • “Cyclic”的含义:放行后,下一轮还可以继续使用。

3.2 与 CountDownLatch 的本质区别

  • CountDownLatch等别人做完(一次性计数到 0)。
  • CyclicBarrier大家相互等(到齐再继续,可复用)。

3.3 代码模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 可选:最后到达的线程执行 barrierAction
System.out.println("all arrived");
});

Runnable worker = () -> {
try {
doStep();
barrier.await(2, TimeUnit.SECONDS);
doNextStep();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
// 超时会导致 barrier broken
} catch (BrokenBarrierException e) {
// 一旦 broken,后续 await 都会失败,除非 reset
}
};

3.4 易错点(很重要)

  • 任一参与线程 中断/超时,barrier 会进入 broken 状态:
    • 其他等待线程会收到 BrokenBarrierException
    • 后续调用 await() 也会立即失败(除非 reset())。

4. Semaphore:许可证(permit)= 并发量

4.1 语义

  • 初始化 permit 数(比如 10)。
  • acquire():拿不到 permit 就阻塞;拿到则 permit–。
  • release():permit++,并可能唤醒等待者。

典型场景:

  • 资源池(连接池、限流、并发调用外部 API)。
  • “最多同时 N 个线程进入临界区”(比锁更像“闸机”)。

4.2 工程模板:必须 finally release

1
2
3
4
5
6
7
8
9
10
Semaphore sem = new Semaphore(10, true); // true: 公平(吞吐略低)

public void callRemote() throws InterruptedException {
sem.acquire();
try {
doRpc();
} finally {
sem.release();
}
}

4.3 面试高频

  • 公平 vs 非公平:公平更少“饿死”,非公平吞吐更高(默认多为非公平)。
  • permit 泄漏 是最常见生产事故:acquire() 后异常路径没 release(),并发量会越来越小直到卡死。

5. Phaser:支持动态参与方的“阶段栅栏”

Phaser 可以理解为 更通用、更现代CyclicBarrier

  • 支持多阶段(phase 0/1/2…)。
  • 支持动态注册/注销参与者(register() / arriveAndDeregister())。

5.1 典型场景

  • 多阶段流水线:阶段 1 所有人完成 → 推进到阶段 2。
  • 参与线程数在运行时变化(任务拆分、动态扩缩容)。

5.2 使用要点

  • 每个阶段:参与者调用 arriveAndAwaitAdvance()
  • 退出必须 arriveAndDeregister(),否则 parties 计数不对会卡住。
1
2
3
4
5
6
7
8
9
10
11
Phaser phaser = new Phaser(3); // 初始注册 3 个 party

Runnable stepper = () -> {
doPhase0();
phaser.arriveAndAwaitAdvance();

doPhase1();
phaser.arriveAndAwaitAdvance();

phaser.arriveAndDeregister();
};

6. Exchanger:两个线程的“交换点”

6.1 语义

  • 两个线程在交换点相遇:
    • A 交出 a,得到 B 的 b
    • B 交出 b,得到 A 的 a

6.2 典型场景

  • 双缓冲:生产者写满一块 buffer 后与消费者交换。
1
2
3
4
5
6
7
8
9
Exchanger<List<String>> exchanger = new Exchanger<>();

// producer
List<String> filled = fill();
List<String> empty = exchanger.exchange(filled, 1, TimeUnit.SECONDS);

// consumer
List<String> toConsume = exchanger.exchange(emptyBuf, 1, TimeUnit.SECONDS);
consume(toConsume);

6.3 易错点

  • 线程数不成对 / 超时:必须设计好超时与重试/降级,否则会永久等待。

7. 总结:同步器与 AQS 的关系

  • CountDownLatch / Semaphore:典型 AQS 共享模式应用。
  • CyclicBarrier:内部主要用 ReentrantLock + Condition 组织等待与唤醒(不是纯 AQS 子类那条路)。
  • Phaser:更复杂的状态推进与等待组织(适合“阶段推进”的问题建模)。

如果你只记一个工程建议:

  • “等待一组任务完成”优先用 CountDownLatch(配合 finally countDown())。
  • “分批同步推进、并且需要复用”优先用 CyclicBarrier / Phaser
  • “限并发/资源池”用 Semaphore

面试题 / Checklist

  • CountDownLatchCyclicBarrier 的核心差异是什么?为什么一个能复用、一个不能?
  • CyclicBarrier 的 broken 状态何时发生?如何恢复/避免?
  • Semaphore 的 permit 泄漏在生产中如何预防?
  • Semaphore 的公平/非公平对吞吐与延迟有什么影响?
  • Phaser 相比 CyclicBarrier 的优势是什么?什么时候必须 deregister?
  • Exchanger 为什么需要“成对到达”?超时策略如何设计?