中 基础
Semaphore使用场景#
一句话答案#
Semaphore 是计数信号量,通过 acquire/release 控制同时访问资源的线程数量,适用于限流和连接池场景。
核心要点
Semaphore 的原理:
- 基于 AQS 共享模式:
state= 剩余许可数 acquire():CAS 把 state 减 1(acquire(n)减 n),不够就入 AQS 队列 parkrelease():CAS 把 state 加回去,并唤醒队列中等待的线程- 公平 / 非公平:
new Semaphore(n, true)公平(先看队列有没有人排队),默认非公平(直接 CAS 抢) - 许可不和线程绑定:A 线程 acquire、B 线程 release 也可以,所以不是可重入锁,也不会检查「谁持有」
常用 API:
acquire()/acquire(n):阻塞获取,可响应中断tryAcquire()/tryAcquire(timeout, unit):拿不到立即返回或超时返回 false,适合「拿不到就降级」acquireUninterruptibly():不响应中断release()/release(n)、availablePermits()
典型用法(控制并发数):
Semaphore semaphore = new Semaphore(5); // 最多允许 5 个线程同时执行
for (int i = 0; i < 20; i++) {
executor.submit(() -> {
try {
semaphore.acquire(); // 获取许可(5 个许可都被占用时阻塞等待)
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
try {
accessDatabase(); // 最多 5 个线程同时执行这段代码
} finally {
semaphore.release(); // 只有 acquire 成功才 release,放 finally 防止许可泄漏
}
});
}java典型场景:
- 数据库等共享资源的并发访问上限
- 调下游接口的并发数限制(配合
tryAcquire(timeout)超时降级) - 有限资源池(如固定数量的打印机、许可证)
- permit=1 时当作互斥锁用(不可重入)
三者对比:
| 维度 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 等待方式 | 一个线程等待其他 N 个线程 | N 个线程互相等待 | 控制并发数量 |
| 是否可重用 | 否(一次性) | 是(自动重置) | 是 |
| 计数器方向 | 递减到 0 触发 | 从 parties 递减到 0 触发(然后自动重置) | 获取/释放许可 |
| 底层实现 | AQS(共享锁) | ReentrantLock + Condition | AQS(共享锁) |
| 典型场景 | 主线程等子任务完成 | 多阶段并行计算 | 资源并发数限制 |
面试回答(2分钟版)
Semaphore 是基于 AQS 实现的计数信号量,通过 acquire 获取许可、release 释放许可来控制同时访问某个资源的线程数量。构造时指定许可总数 N,每个线程 acquire 成功则许可减一,许可耗尽后其他线程阻塞等待,直到有线程 release 归还许可。典型使用场景包括数据库连接池限流、接口并发数控制等。和 CountDownLatch、CyclicBarrier 的区别在于:CountDownLatch 是一次性的倒计时门闩,一个线程等 N 个子任务完成;CyclicBarrier 是可重用的栅栏,N 个线程互相等待全部到达后同时继续;Semaphore 是控制并发资源数量,许可可以反复获取释放。底层都基于 AQS,CountDownLatch 和 Semaphore 用共享模式,CyclicBarrier 用 ReentrantLock 加 Condition 实现。使用 Semaphore 时注意在 finally 中 release 防止泄漏,permit 设为 1 时就退化成互斥锁。支持公平和非公平模式,公平模式按等待顺序获取许可,非公平模式允许插队吞吐更高。
追问与易错
追问方向:
- “Semaphore 能实现互斥锁吗?”→ 可以,permit 设为 1 就退化为互斥锁,但不支持可重入,通常用 ReentrantLock 更合适
- “公平和非公平的区别?”→ 公平模式按等待队列顺序获取许可,保证不饥饿但吞吐低;非公平模式允许插队,吞吐更高但可能饥饿,默认非公平
- “限流用 Semaphore 还是令牌桶?”→ Semaphore 适合控制并发数(如连接池),令牌桶适合控制请求速率(QPS);两者维度不同,单机并发控制用 Semaphore,接口限速用 Guava RateLimiter
易错点:
- ❌ Semaphore 只能用于限流——还能用于资源池
- ❌ 忘记在 finally 中 release