CountDownLatch、CyclicBarrier、Semaphore 的区别?

2025年 阅读约 10 分钟 面试指南 · Java面试 · Java并发

深入对比三大并发工具类:CountDownLatch(倒计数门闩,一次性)、CyclicBarrier(循环栅栏,可重用)、Semaphore(信号量,控制并发数),从原理、使用场景、核心区别到源码分析,附完整示例和面试模拟。

一句话总结

三者都是 JUC 的并发工具类,基于 AQS 实现:CountDownLatch — 倒计数门闩,一个线程等待其他线程完成(countDown),一次性,不可重置;CyclicBarrier — 循环栅栏,一组线程互相等待到齐后一起执行(await),可重用,支持回调;Semaphore — 信号量,控制同时访问资源的线程数量(acquire/release),用于限流。

初级理解

CountDownLatch — 倒计数门闩

一个线程(或多个)等待其他线程完成任务,计数减到 0 时释放所有等待线程。

// 场景:主线程等待 3 个子线程全部完成 CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { doWork(); latch.countDown(); // 计数 -1 }).start(); } latch.await(); // 阻塞直到计数为 0 System.out.println("所有子线程完成");

CyclicBarrier — 循环栅栏

一组线程互相等待,所有线程都到达栅栏后一起继续执行。

// 场景:3 个线程都到达后一起出发 CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("所有线程到齐,出发!"); // 回调 }); for (int i = 0; i < 3; i++) { new Thread(() -> { prepare(); barrier.await(); // 等待其他线程 runTogether(); // 一起执行 }).start(); }

Semaphore — 信号量

控制同时访问资源的线程数量,用于限流。

// 场景:最多 3 个线程同时访问资源 Semaphore semaphore = new Semaphore(3); // 3 个许可 for (int i = 0; i < 10; i++) { new Thread(() -> { semaphore.acquire(); // 获取许可(可能阻塞) try { accessResource(); } finally { semaphore.release(); // 释放许可 } }).start(); }
一句话总结:CountDownLatch = 等别人完成,CyclicBarrier = 互相等到齐,Semaphore = 限流控制。

中级深入

核心区别对比

特性CountDownLatchCyclicBarrierSemaphore
可重用❌ 一次性✅ 可重置✅ 可释放许可
计数方向递减(countDown)递增(await 计数+1)递减(acquire)
回调❌ 无✅ barrierAction❌ 无
等待线程一个等多个 / 多个等一个线程互相等待线程等待许可
AQS 模式共享模式ReentrantLock + Condition共享模式

CountDownLatch 源码分析

// CountDownLatch 基于 AQS 共享模式 public class CountDownLatch { private final Sync sync; // await() → AQS.acquireSharedInterruptibly() public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } // countDown() → AQS.releaseShared() public void countDown() { sync.releaseShared(1); } // Sync 的 tryAcquireShared:state == 0 时获取成功 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } // Sync 的 tryReleaseShared:state 减 1,减到 0 时释放 protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; int nextc = c - 1; if (compareAndSetState(c, nextc)) return nextc == 0; // 减到 0 时唤醒所有等待线程 } } }

CyclicBarrier 源码分析

// CyclicBarrier 基于 ReentrantLock + Condition public class CyclicBarrier { private final ReentrantLock lock = new ReentrantLock(); private final Condition trip = lock.newCondition(); private final int parties; // 参与线程数 private int count; // 当前等待计数 private Runnable barrierCommand; private int dowait() { lock.lock(); try { int index = --count; if (index == 0) { // 最后一个到达的线程 if (barrierCommand != null) barrierCommand.run(); // 执行回调 trip.signalAll(); // 唤醒所有等待线程 // 重置 count,实现可重用 count = parties; return 0; } // 不是最后一个,等待 trip.await(); } finally { lock.unlock(); } } }

Semaphore 源码分析

// Semaphore 基于 AQS 共享模式 public class Semaphore { private final Sync sync; // acquire() → AQS.acquireSharedInterruptibly() public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } // release() → AQS.releaseShared() public void release() { sync.releaseShared(1); } // 公平模式:先检查队列中是否有等待线程 protected int tryAcquireShared(int acquires) { for (;;) { if (hasQueuedPredecessors()) return -1; // 公平检查 int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } }
中级要点:CountDownLatch 和 Semaphore 基于 AQS 共享模式,CyclicBarrier 基于 ReentrantLock + Condition。CyclicBarrier 可重用是因为最后一个线程到达后会重置 count。

高级拓展

CyclicBarrier 的 broken 状态

如果某个线程在 await 时被中断或超时,CyclicBarrier 会进入 broken 状态,其他等待线程会收到 BrokenBarrierException。此时需要调用 reset() 重置。

// CyclicBarrier 的异常处理 try { barrier.await(5, TimeUnit.SECONDS); // 超时等待 } catch (TimeoutException e) { // 超时会导致 barrier 进入 broken 状态 barrier.reset(); // 重置 } catch (BrokenBarrierException e) { // 其他线程导致 broken }

Semaphore 的公平模式

Semaphore 支持公平和非公平两种模式。公平模式下,acquire 按 FIFO 顺序获取许可;非公平模式下,新来的线程可以"插队"直接 CAS 获取许可。

实际应用场景

工具典型场景
CountDownLatch并行任务完成后汇总、服务启动依赖检查
CyclicBarrier多线程分批计算后合并、游戏多人匹配
Semaphore数据库连接池限流、API 限流、停车场管理
高级加分项:能说出 CyclicBarrier 的 broken 状态和处理方式,知道 Semaphore 的公平/非公平模式,能根据场景选择合适的工具。

实战场景

场景一:CountDownLatch — 并行查询汇总

public Map<String, Object> parallelQuery() throws Exception { CountDownLatch latch = new CountDownLatch(3); Map<String, Object> result = new ConcurrentHashMap<>(); new Thread(() -> { result.put("user", queryUser()); latch.countDown(); }).start(); new Thread(() -> { result.put("order", queryOrder()); latch.countDown(); }).start(); new Thread(() -> { result.put("product", queryProduct()); latch.countDown(); }).start(); latch.await(3, TimeUnit.SECONDS); // 最多等 3 秒 return result; }

场景二:Semaphore — 简易限流器

public class RateLimiter { private final Semaphore semaphore; public RateLimiter(int maxConcurrent) { this.semaphore = new Semaphore(maxConcurrent); } public boolean tryAcquire(long timeout, TimeUnit unit) { try { return semaphore.tryAcquire(timeout, unit); } catch (InterruptedException e) { return false; } } public void release() { semaphore.release(); } }

面试模拟

Q:CountDownLatch 和 CyclicBarrier 的区别?

A:CountDownLatch 是一次性的,计数递减,一个线程等待其他线程完成(或反过来);CyclicBarrier 是可重用的,计数递增,一组线程互相等待到齐后一起执行,支持 barrierAction 回调。CountDownLatch 基于 AQS 共享模式,CyclicBarrier 基于 ReentrantLock + Condition。

Q:Semaphore 的原理是什么?

A:Semaphore 基于 AQS 共享模式,state 表示剩余许可数。acquire() 尝试减少 state,如果 state < 0 则线程入队阻塞;release() 增加 state 并唤醒等待线程。支持公平/非公平模式,常用于限流和资源池管理。

Q:CyclicBarrier 的 broken 状态是什么?

A:当某个线程在 await 时被中断、超时或发生异常,CyclicBarrier 进入 broken 状态。此时其他等待线程会收到 BrokenBarrierException。需要调用 reset() 重置才能继续使用。