← 返回题目列表

CountDownLatch、CyclicBarrier、Semaphore 有什么区别?

高频 中等 第 9 / 31 题 更新于 2026/07/26
CountDownLatchCyclicBarrierSemaphoreAQS

简化版

三者都是 JUC 里基于 AQS 的同步工具,但用途不同:CountDownLatch(倒计时门闩)——一个或多个线程等待另一批线程都完成,计数减到 0 就放行,一次性、不可重置;CyclicBarrier(循环栅栏)——一批线程互相等待,都到齐了再一起继续,可循环复用Semaphore(信号量)——控制同时访问某资源的线程数量(限流/连接池),拿到许可才能进,用完释放。一句话:Latch 等别人、Barrier 互相等、Semaphore 控并发数。

详细版

维度CountDownLatchCyclicBarrierSemaphore
作用等一组任务全部完成一组线程互相等待、集合后齐步走控制同时访问的线程数
计数方向递减(countDown 到 0)递增到 N 触发许可增减(acquire/release)
是否可重用否(减到 0 作废)是(自动重置,可循环)是(许可反复借还)
谁在等调 await 的线程等 count 归零每个线程到 barrier 都等其他线程拿不到许可的线程等别人 release
底层AQS 共享模式ReentrantLock + ConditionAQS 共享模式
典型场景主线程等所有子任务、服务启动等依赖就绪多线程分阶段计算、并发测试起跑线限流、数据库连接池、停车场
// CountDownLatch:主线程等 3 个子任务都完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    pool.submit(() -> { doWork(); latch.countDown(); });  // 每完成一个减 1
}
latch.await();   // 阻塞直到 count == 0
System.out.println("全部完成");

// CyclicBarrier:3 个线程都到齐才一起继续,可循环
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("集合完毕"));
// 每个线程:barrier.await();  → 3 个都调用了才一起放行,之后 barrier 自动重置

// Semaphore:最多 2 个线程同时进入
Semaphore sem = new Semaphore(2);
sem.acquire();   // 拿一个许可,没有就阻塞
try { doLimitedWork(); } finally { sem.release(); }  // 用完必须释放

⚠️ CountDownLatch 是一次性的——count 减到 0 后再 countDown 无效、await 立即返回,不能重置;需要「反复用同一个栅栏」的场景用 CyclicBarrier。

完整版教学

一、一句话抓住三者的本质差异

死记表格容易混,抓住「谁等谁」就不会错:

CountDownLatch:  A 线程 ──等待──▶ B、C、D 完成(单向:等待者 vs 被等待者,两拨人)
CyclicBarrier:   A、B、C 互相等待,都到齐再一起走(对称:同一拨人彼此等)
Semaphore:       N 个许可,抢到的进、抢不到的等别人还(等的是"资源名额")

CountDownLatch 是「裁判等运动员」——运动员(子线程)各自跑完 countDown,裁判(主线程)await 到全部完成才宣布结果;CyclicBarrier 是「运动员互相等」——所有人到起跑线才一起发枪;Semaphore 是「停车场限位」——车位满了就在门口等,有车开走才放一辆进。

二、CountDownLatch:递减的一次性门闩

内部是 AQS 的 state 存计数值。await() 让线程进入等待(state≠0 时阻塞),countDown() 用 CAS 把 state 减 1,减到 0 时唤醒所有等待线程。

初始 count = 3
子线程1 countDown → 2
子线程2 countDown → 1
子线程3 countDown → 0  → 唤醒所有 await 的线程
主线程 await 一直阻塞,直到这一刻才返回

关键特性:减到 0 就永久失效,无法重置。它有两种经典用法——① 主线程等 N 个子任务(上面的例子);② 反过来,N 个子线程 await 同一个 count=1 的 latch,主线程一 countDown 就让它们「同时起跑」(模拟并发压测的瞬时并发)。因为不可重用,一轮任务用一个新 latch。

三、CyclicBarrier:可循环的集合点

CyclicBarrier 内部是 ReentrantLock + Condition + 一个递增计数。每个线程调 await() 时计数加 1 并阻塞;当第 N 个线程到达(计数满),触发可选的 barrierAction,然后唤醒所有线程一起继续,并自动重置计数,可以进入下一轮。

第 1 轮:线程A await(1/3) 阻塞
        线程B await(2/3) 阻塞
        线程C await(3/3) → 满!执行 barrierAction → 三人一起放行 → 计数重置为 0
第 2 轮:又可以重新用同一个 barrier 集合...(Cyclic = 循环)

它专治「分阶段并行计算」:把大任务切成 N 份并行算,每个阶段结束用 barrier 集合,等所有分片都算完这一阶段再一起进下一阶段。与 Latch 的核心区别就俩字:可循环,且等待者就是参与者本身(互相等,没有独立的裁判)。

四、Semaphore:许可就是并发名额

Semaphore 维护一组「许可(permit)」,基于 AQS 共享模式。acquire() 拿一个许可(state 减 1),没有许可就阻塞;release() 归还许可(state 加 1)并唤醒等待者。它控制的是「同时有多少线程能进入」。

Semaphore(3) —— 3 个许可,等价于 3 条并发通道
线程1 acquire → 剩 2 → 进入
线程2 acquire → 剩 1 → 进入
线程3 acquire → 剩 0 → 进入
线程4 acquire → 无许可 → 阻塞等待
线程1 release → 剩 1 → 唤醒线程4,它拿到许可进入

典型用途:限流(最多 N 个请求同时处理)、资源池(数据库连接池、对象池,池大小 = 许可数)、接口保护(下游只能扛 N 并发,用 Semaphore 兜住)。它和 Latch/Barrier 最大不同:关注的不是”等待完成”,而是”限制同时进入的数量”,许可可反复借还。

五、三者用数字对比一次并发场景

用「5 个线程处理任务」直观感受差异:

需求用哪个行为
主线程等这 5 个都干完再汇总CountDownLatch(5)主线程 await,5 次 countDown 后放行
这 5 个分 3 阶段,每阶段都要对齐CyclicBarrier(5)每阶段结束 5 个互等,齐了再进下一阶段
这 5 个任务,但下游最多扛 2 并发Semaphore(2)同时只有 2 个在跑,其余排队

一个容易混的点:CountDownLatch 的 count 和线程数不一定相等——count 是「要等的事件数」,一个线程可能 countDown 多次,或多个线程 countDown 同一次。而 CyclicBarrier 的 N 就是「参与互等的线程数」。

六、底层与选型总结

三者都基于 AQS 家族,但实现路径不同:Latch 和 Semaphore 直接用 AQS 共享模式(state 存计数/许可),CyclicBarrier 用 ReentrantLock+Condition(因为它需要「重置」这种更复杂的协调)。选型口诀:

要「等一批任务完成」            → CountDownLatch(一次性)
要「一批线程反复集合、分阶段」  → CyclicBarrier(可循环)
要「限制同时并发数」            → Semaphore(限流/资源池)

记忆钩子:「Latch 减到 0 放行(裁判等运动员,一次性)、Barrier 到齐才走(运动员互等,可循环)、Semaphore 借许可(停车场限位,可重借)」

七、常见误区与追问

  • 误区:CountDownLatch 可以重置反复使用。 不能,count 减到 0 就永久失效;要循环复用的集合点用 CyclicBarrier。
  • 误区:CyclicBarrier 和 CountDownLatch 只是可重用的区别。 还有「谁等谁」不同——Latch 是等待者和被等待者两拨人,Barrier 是同一拨线程互相等。
  • 误区:Semaphore 只能做限流。 也常用作资源池(许可数=池容量)、开关(0/1 许可当互斥锁)等,本质是「名额管理」。
  • 误区:CountDownLatch 的初始值必须等于线程数。 它是「要等的事件次数」,一个线程可多次 countDown,与线程数无必然关系。
  • 追问:CyclicBarrier 的某个线程超时或被中断会怎样? 会打破栅栏(BrokenBarrier),其他在 await 的线程抛 BrokenBarrierException,需要 reset 或重建才能继续。
  • 追问:Semaphore 的公平与非公平有什么区别? 公平模式按 FIFO 顺序分配许可(避免饥饿但吞吐略低),非公平模式允许插队(吞吐高,默认);构造时可指定。
  • 追问:CountDownLatch 能替代 join 吗? 能且更灵活——join 只能等整个线程结束,CountDownLatch 可在线程内任意时机 countDown,还能让主线程等「部分完成」或多个线程池任务。

八、加强记忆

三个同步工具用「谁等谁 + 能否重用」两把尺子就能分清:CountDownLatch 是「裁判等运动员」——独立的等待者 await 阻塞,被等的线程各自 countDown,减到 0 放行且一次性作废,用于主线程等一批子任务或让子线程同时起跑;CyclicBarrier 是「运动员互相等」——同一拨线程都 await 到齐才一起走、可循环复用、还能带集合动作,用于分阶段并行计算;Semaphore 是「停车场限位」——acquire 拿许可、release 还许可,控制同时进入的数量、许可可反复借还,用于限流和资源池。底层上 Latch 和 Semaphore 走 AQS 共享模式,Barrier 走 ReentrantLock+Condition。记住一句「Latch 减到 0 放行(一次性)、Barrier 到齐才走(可循环)、Semaphore 借许可控并发」,这道对比题就稳了。