semaphore控制并发数核心是acquire()和release()配try-finally,它作为“许可证桶”支持固定并发上限,语义清晰且避免死锁;必须在finally中release,否则许可证永久丢失。

Java里用Semaphore控制并发数,核心就两步
直接上结论:用 Semaphore 的 acquire() 和 release() 配合 try-finally,就能稳住并发上限。它不是锁,而是“许可证桶”——桶里有几把钥匙,就允许多少线程同时进临界区。
为什么不用synchronized或ReentrantLock来限流
因为它们是「互斥」工具,目标是保证线程安全;而限流要的是「允许N个线程并行,超了就等」——Semaphore 天然支持这个语义。用锁硬凑限流,容易写成串行,或者漏掉 unlock() 导致死锁。
-
synchronized只能 1 个线程进入,没法设成 5 个、10 个 -
ReentrantLock默认也是独占,虽然可配fair = true,但依然不提供「固定许可数」的抽象 -
Semaphore(5)明确表达“最多 5 个并发”,语义清晰,不易误用
实战中必须加try-finally,否则许可证永远回不来
这是最常踩的坑:没在 finally 里调 release(),一次异常就导致许可证永久丢失,后续所有线程卡在 acquire() 上不动。
Semaphore semaphore = new Semaphore(3);
// ✅ 正确写法
try {
semaphore.acquire(); // 拿许可证
// 执行耗时操作,比如HTTP调用、DB查询
doSomethingExpensive();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 必须放这里!不管成功失败都要还
}
- 不要把
release()放在 try 块末尾——异常会跳过它 - 不要在 catch 里 release —— 成功执行后没地方 release
- 如果业务逻辑本身 throw RuntimeException,也要靠 finally 拦住
acquire() vs tryAcquire():阻塞还是非阻塞,看场景选
前者会一直等(可能被中断),后者立刻返回布尔值,适合做快速失败或降级。
- API网关限流、下游服务保护 → 用
tryAcquire(),超限时直接返回 429 或走熔断 - 内部批处理任务协调(如并发拉取10个文件)→ 用
acquire(),等一等没问题 - 想设超时避免无限等待?用
tryAcquire(long timeout, TimeUnit unit) - 注意:
acquireUninterruptibly()不响应中断,慎用,调试和运维会很难受
Semaphore 的公平性(new Semaphore(5, true))一般不用开——它会让等待队列变长,吞吐下降,除非你明确需要“先到先得”的严格顺序。大多数限流场景,非公平模式更高效。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










