Semaphore管理可计数的许可而非权限,核心是控制并发线程数以保护下游资源;许可数需对标真实瓶颈并留缓冲,acquire/release须紧贴核心操作,优先使用带超时的tryAcquire并配合finally释放,还可叠加简易熔断提升稳定性。

Semaphore 不分配“权限”,它管理的是“许可”——一种可计数、可原子操作的资源准入凭证。核心目标是控制并发线程数,防止下游资源(如数据库连接、第三方接口、硬件设备)被压垮,同时避免许可闲置导致吞吐不足。用对了,是稳压阀;用错了,可能变成单点阻塞源。
许可数设置:参考下游容量,留出缓冲余量
初始 permits 值不是拍脑袋定的,而是对标真实资源瓶颈:
- 若数据库连接池最大为 20,Semaphore 可设为 15~18,预留空间应对连接异常回收延迟或监控探针等辅助请求
- 若调用一个 QPS 上限为 300 的外部 API,按平均响应耗时 200ms 推算,理论并发≈60,再打 7 折设为 40 更稳妥
- 设得太小(如仅 2)会导致大量线程空等,CPU 利用率低、RT 升高;设得太大(如 100)等于没限流,可能瞬间打崩下游
作用范围收缩:只围住真正耗资源的操作
acquire() 和 release() 必须紧贴核心资源调用,不能包裹整段业务逻辑:
- 错误做法:Controller 入口就 acquire,结果参数校验失败、鉴权不通过,许可却被占着不放
- 正确做法:校验 & 日志 & 缓存判断完成之后,再 acquire;远程调用返回、数据解析完毕后,立即 release
- 这样许可周转更快,单位时间能处理更多有效请求,资源利用率更真实
获取方式升级:优先用带超时的 tryAcquire
阻塞式 acquire() 在生产环境风险极高,易引发线程池雪崩:
- 推荐使用 tryAcquire(200, TimeUnit.MILLISECONDS),超时建议 100–300ms
- 返回 false 时,可快速降级(返回缓存值、空响应、友好提示),不卡主线程
- 务必配合 finally 块确保 release() 执行,否则许可永久泄漏,信号量很快变“死锁”
- 避免使用 acquireUninterruptibly() —— 它屏蔽中断,运维无法通过 Thread.interrupt() 安全摘除问题线程
增强稳定性:加一层简易熔断保护
Semaphore 本身不熔断,但可以低成本补上这一环:
- 用 AtomicInteger 统计最近 60 秒总请求数和失败数(如远程超时、5xx)
- 当失败率 ≥50% 且总请求数 ≥20,自动切到 OPEN 状态
- OPEN 状态下直接拒绝新请求(跳过 acquire),30 秒后进 HALF-OPEN,只放行 1–2 个试探请求
- 试探成功则恢复服务;失败则重置熔断计时器,继续保护系统

















