Skip to content
基础

一句话答案

Semaphore 是计数信号量,通过 acquire/release 控制同时访问资源的线程数量,适用于限流和连接池场景。

核心要点

CountDownLatch(倒计时门闩):

java
// 场景:主线程等待 N 个子任务全部完成后再继续

CountDownLatch latch = new CountDownLatch(3);  // 计数器 = 3

// 子任务完成后调用 countDown()
executor.submit(() -> { doTask1(); latch.countDown(); });
executor.submit(() -> { doTask2(); latch.countDown(); });
executor.submit(() -> { doTask3(); latch.countDown(); });

latch.await();  // 主线程阻塞,直到计数器减到 0
System.out.println("所有子任务完成");

// 特点:一次性的,计数器减到 0 后不能重置
// 典型场景:启动时等待多个服务初始化完成、并行查询多个数据源后汇总结果

CyclicBarrier(循环栅栏):

java
// 场景:N 个线程互相等待,全部到达栅栏后同时继续

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达栅栏,执行汇总逻辑");
});

for (int i = 0; i < 3; i++) {
    executor.submit(() -> {
        doPartialWork();
        barrier.await();  // 每个线程在这里等待,直到 3 个线程都到达
        doNextPhase();     // 所有线程同时继续
    });
}

// 特点:可重用(自动重置),适合多阶段并行计算
// 典型场景:多线程分阶段计算(如 MapReduce 的每轮迭代)

Semaphore(信号量):

java
// 场景:控制同时访问某个资源的线程数(限流)

Semaphore semaphore = new Semaphore(5);  // 最多允许 5 个线程同时执行

for (int i = 0; i < 20; i++) {
    executor.submit(() -> {
        semaphore.acquire();  // 获取许可(如果 5 个许可都被占用,阻塞等待)
        try {
            accessDatabase();  // 最多 5 个线程同时执行这段代码
        } finally {
            semaphore.release();  // 释放许可
        }
    });
}

// 典型场景:数据库连接池限流、接口并发数限制

三者对比:

维度CountDownLatchCyclicBarrierSemaphore
等待方式一个线程等待其他 N 个线程N 个线程互相等待控制并发数量
是否可重用否(一次性)是(自动重置)
计数器方向递减到 0 触发递增到 N 触发获取/释放许可
底层实现AQS(共享锁)ReentrantLock + ConditionAQS(共享锁)
典型场景主线程等子任务完成多阶段并行计算资源并发数限制
追问与易错

追问方向:

  • Semaphore 能实现互斥锁吗?
  • 公平和非公平的区别?
  • 限流用 Semaphore 还是令牌桶?

易错点:

  • ❌ Semaphore 只能用于限流——还能用于资源池
  • ❌ 忘记在 finally 中 release

💡 记忆锚点

Semaphore 就是停车场入口的计数器:总共 N 个车位,acquire 进一辆减一个,release 出一辆加一个,车位满了后面的车就在门口排队等。既能限流(接口并发控制)也能当资源池用(连接池限制),permit 设为 1 就退化成互斥锁。