Java中用Semaphore控并发,关键在异常下许可管理、中断响应和资源释放的确定性;必须用try-finally确保release执行,acquire放try外,正确处理InterruptedException,设置超时并降级,按场景选公平性。

Java 中用 Semaphore 控制并发请求数,关键不在“能不能限”,而在“限得稳不稳”。真正影响健壮性的,是异常路径下的许可管理、线程中断响应、以及资源释放的确定性。稍有疏忽,就会出现许可证永久丢失、请求堆积、或系统逐渐卡死。
必须用 try-finally 包裹 acquire/release
这是最基础也最容易被忽略的一环。acquire() 可能阻塞,但更危险的是:一旦任务执行中抛出异常(比如网络超时、NPE、IO异常),若没在 finally 里 release,那个许可就永远回不来了。
- 后果:Semaphore 内部计数器持续减少,可用许可越来越少,后续所有线程在 acquire() 处无限等待
- 正确写法:acquire() 放在 try 块外(避免 acquire 本身被中断时误 release),release() 必须在 finally 块中
- 示例:
semaphore.acquire(); // 不在 try 内 try { doWork(); } finally { semaphore.release(); // 无论如何都执行 }
主动处理 InterruptedException
acquire() 和 acquireUninterruptibly() 的区别,决定了你的服务是否对中断敏感。生产环境建议保留中断能力,而不是屏蔽它。
- acquire() 会响应 Thread.interrupt(),抛出 InterruptedException,并自动释放已获取的许可(如果有的话)
- 但你仍需在 catch 块中恢复中断状态:Thread.currentThread().interrupt()
- 不要简单吞掉异常或只打日志——这会让上层调度器无法感知任务已被取消
- 若业务逻辑明确不允许中断(如关键事务),再考虑 acquireUninterruptibly(),但需清楚代价:线程可能无法被优雅终止
设置超时避免无限等待
不限制 acquire 等待时间,等于把系统稳定性交给下游响应——一个慢接口或死锁,就能拖垮整个限流机制。
立即学习“Java免费学习笔记(深入)”;
- 用 tryAcquire(long timeout, TimeUnit unit) 替代无参 acquire()
- 超时后应明确降级策略:返回失败码、走缓存、记录告警,而不是直接丢弃或重试
- 注意:超时返回 false 时,一定不能调用 release()(因为根本没拿到许可)
- 示例:
if (!semaphore.tryAcquire(3, TimeUnit.SECONDS)) { log.warn("Request rejected: concurrency limit reached or timeout"); throw new ServiceUnavailableException("Too many requests"); } try { doWork(); } finally { semaphore.release(); }
公平性选择要匹配场景特征
默认非公平模式性能更好,但可能让某些请求长期排队;公平模式保证 FIFO,却带来额外调度开销。
- 高吞吐 API 网关、批量任务调度:优先非公平(默认),牺牲一点公平换取响应速度
- 金融类强顺序场景、用户关键操作链路:启用公平模式 new Semaphore(5, true),防止个别请求饿死
- 注意:公平性只影响等待队列中的线程获取顺序,不影响已运行线程的执行时长


















