semaphore 是并发数控制器而非 qps 限流器,用于同步阻塞接口的线程数限制,需显式非公平模式、超时获取、配对释放,并配合降级与监控防许可泄漏。

Semaphore 在 Java 中不是“QPS 限流器”,而是并发数控制器——它限制的是同一时刻正在执行业务逻辑的线程数量,适用于同步阻塞型接口的轻量级保护,用对了能防雪崩,用错了反而拖垮线程池。
只用于同步阻塞接口
你的接口必须满足:请求进来后,整个处理流程(含远程调用、DB 操作、计算)都在同一线程内完成,不跨线程回调或异步调度。
- ✅ 合适:Spring MVC + Tomcat 默认同步模型、传统 Servlet 接口
- ❌ 不合适:Spring WebFlux、Vert.x、gRPC 异步服务;或用了 @Async、CompletableFuture 的接口——此时 acquire() 可能阻塞在 IO 线程上,拖垮容器线程池
- ⚠️ 耗时低于 5ms 的接口慎用:Semaphore 自身的 CAS 开销可能反成瓶颈
初始化与获取必须规范
别依赖默认行为,显式声明非公平模式 + 超时尝试,避免无限等待和长尾延迟。
- 用 new Semaphore(20, false),不用 new Semaphore(20) 或 new Semaphore(20, true):公平模式吞吐下降 30%~50%,高并发下容易卡住个别请求
- 永远用 tryAcquire(100, TimeUnit.MILLISECONDS),而不是 acquire();超时时间建议设为接口 P99 响应时间的 1.2~1.5 倍
- 许可数不能拍脑袋定:先压测,观察 DB 连接池使用率、CPU 利用率、GC 频次,逐步调到资源不打满但吞吐最优的值
release 必须配对且放对位置
许可泄漏是 Silent Killer:一旦某次异常没 catch 到、或 release 被跳过,剩余许可数永久减少,Semaphore 会逐渐变成“空转红灯”,所有新请求直接失败,监控却无明显指标异常。
- release() 必须写在 try-finally 最外层,哪怕业务逻辑抛出 RuntimeException 也要确保执行
- 不要在异步回调里漏掉 release:如果业务启动了新线程或 Future,务必把 release 移到回调完成处,否则主线程 release 就算错
- 可加轻量日志:每次 acquire 成功/失败、release 前后打点,配合 ELK 快速定位泄漏点
结合降级做阶梯式响应
获取许可失败时,不建议简单返回 429,而应按业务重要性分级响应:
- 核心链路:快速返回兜底数据或缓存结果
- 非核心链路:返回明确提示(如“当前繁忙,请稍后再试”)
- 持续失败时触发熔断开关,自动关闭该接口入口并告警
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











