semaphore本质是控制并发访问临界资源的流量闸门,需精准围住耗资源操作、合理设许可数、带超时获取并finally释放,结合失败统计可实现简易熔断。

Semaphore 不是用来“分配权限”,而是通过许可计数器控制同时能访问临界资源的线程数量,本质是资源容量的流量闸门。用对了,它能提升吞吐、防止雪崩;用错了,轻则限流失效,重则许可泄漏、库存超卖、接口全线阻塞。
明确临界资源边界,只围住真正耗资源的操作
临界资源是指一次只能由有限数量线程安全使用的共享资源,比如数据库连接、第三方 API 调用、文件句柄或秒杀库存扣减逻辑。关键不是“整个接口”,而是其中可能引发竞争或压垮下游的那几行。
- ✅ 正确做法:参数校验、日志记录、鉴权等轻量操作放在 acquire 之前;acquire 紧贴远程调用或 DB 更新前,release 紧跟返回解析后、响应组装前
- ❌ 错误做法:Controller 入口就 acquire,结果参数非法被拒,许可却被占着不放,白白浪费并发额度
- 举例:秒杀场景中,只对「查库存→扣库存→写订单」这一组非原子但必须受控的操作加 Semaphore,而不是包裹整个 HTTP 请求生命周期
许可数设多少?参考下游真实容量,留缓冲余量
初始 permits 值不是拍脑袋定的,它直接对应你希望并发执行该临界区的最大线程数,应与下游资源瓶颈对齐。
- 若数据库连接池大小为 20,Semaphore 建议设为 15~18——留出 2~5 个连接给管理任务、后台作业或突发流量缓冲
- 设太小(如仅设 2):线程大量排队空等,CPU 和线程池闲置,吞吐上不去
- 设太大(如设 100 但 DB 连接池只有 20):大量 acquire 成功后在 getConnection() 阶段阻塞,实际造成连接池饿死,错误集中爆发
必须带超时获取 + finally 强制释放
无超时的 acquire() 或漏掉 release() 是生产事故高发区。前者会导致线程无限等待,拖垮线程池;后者造成许可永久泄漏,很快耗尽信号量,后续所有请求卡死。
- 优先使用 tryAcquire(long timeout, TimeUnit unit),超时建议 100–300ms,超时返回 false 后可快速降级(返回缓存、默认值或友好提示)
- acquire() 必须与 release() 成对出现在 try-finally 块中,哪怕业务逻辑抛异常,也能确保许可归还
- 禁用 acquireUninterruptibly() 和无参 acquire(),它们掩盖了等待风险,不利于故障定位和熔断响应
结合失败统计做简易熔断,让限流更智能
Semaphore 本身不熔断,但你可以用它+原子计数器构建轻量级保护层,在持续失败时主动暂停放行,避免无效请求反复冲击已脆弱的下游。
- 用 AtomicInteger 统计最近 60 秒内总请求数和失败数
- 当失败率 ≥50% 且总请求数 ≥20,状态切为 OPEN,新请求直接拒绝(跳过 acquire)
- OPEN 持续 30 秒后进入 HALF-OPEN,只允许少量试探请求通过;试探成功则恢复服务,失败则重置熔断计时











