semaphore管理“许可”而非“权限”,通过可配置计数器控制并发线程数,许可数需匹配下游真实资源容量(如数据库连接池20则设15~18),acquire/release须精准包裹耗资源操作并带超时与finally释放,配合失败统计可实现简易熔断。

Semaphore 不是分配“权限”,而是管理“许可”——它用一个可配置的计数器,控制同一时刻能进入临界区的线程(或协程)数量,从而在不压垮下游的前提下,把资源利用率拉到最优。
许可数要贴合真实资源容量
设得太小,资源空转、吞吐上不去;设得太大,限流形同虚设,容易打爆数据库连接池、第三方 API 或文件句柄。关键不是拍脑袋定数字,而是看下游瓶颈:
- 数据库连接池大小是 20 → Semaphore 初始值建议设 15~18,留出缓冲余量
- HTTP 客户端最大并发调用配额为 50 → 可设为 40~45,应对突发抖动
- 磁盘 IO 吞吐在 100MB/s 时达到饱和 → 根据单次任务平均耗时反推合理并发数,再设许可
只围住真正耗资源的操作
acquire() 和 release() 必须精准包裹核心动作,比如远程调用、DB 写入、大文件解析——而不是整个接口入口。轻量逻辑(参数校验、日志记录、鉴权)应放在 acquire 之前。
- 错误做法:Controller 一进来就 tryAcquire(),结果参数校验失败也占着许可
- 正确做法:校验通过后、发起远程请求前 acquire;响应返回后、组装结果前 release
- 好处:许可不被空转占用,单位时间能处理更多有效请求
必须带超时 + finally 释放
无超时的 acquire() 在异常或下游长时间无响应时,会导致线程永久阻塞,最终拖垮线程池。生产环境严禁使用 acquireUninterruptibly() 或无参 acquire()。
- 优先用 tryAcquire(long timeout, TimeUnit unit),超时建议 100–300ms
- 超时返回 false,立即走降级路径(如返回缓存、默认值或友好提示)
- release() 必须放在 finally 块里,确保任何路径下都不会遗漏,防止许可泄漏
搭配失败统计做简易熔断
Semaphore 本身不熔断,但可以低成本实现“感知式限流”:
- 用 AtomicInteger 统计最近 60 秒的总请求数和失败数
- 失败率 ≥50% 且总请求数 ≥20 → 开关切到 OPEN 状态
- OPEN 状态直接拒绝新请求(跳过 acquire),30 秒后进 HALF-OPEN,放行少量试探请求
- 试探成功则恢复,失败则重置熔断计时器
不复杂但容易忽略











