semaphore本质是可配置许可数的计数器,用于控制单机内并发线程数,适用于后台任务、第三方调用限流等轻量场景;必须全局唯一、acquire/release严格配对且置于finally中,超时建议100~300ms,并配合失败统计实现简易熔断。

Semaphore 信号量不是用来“锁住资源”的,而是用来“控制同时能干活的线程数”的。它本质是一个可配置数量的许可计数器,适合在单机、低QPS、强依赖外部系统等场景中做轻量级并发调节,关键在于把许可用在刀刃上、配对稳、不泄漏。
适用场景要拎得清
它只在当前 JVM 内生效,不跨实例、不自适应、不分布式。别拿它去扛云原生多副本或 QPS 过百的接口——那该用 Sentinel 或 Redis+Lua。真正适合它的典型场景有:
- 后台管理类接口,比如定时触发的批量导出任务,每台机器最多跑 3 个
- 调用核心第三方系统(如银行/保险接口),对方硬性要求单机并发 ≤5 路
- 轻量服务,无中间件依赖,QPS 稳定在 10 以内,且对限流精度要求不高
初始化与使用必须严谨
一个 Semaphore 实例必须全局唯一、长生命周期存在。Spring 中应声明为 @Bean,切忌每次 new。更关键的是 acquire 和 release 必须由同一线程完成,且严格配对:
- 禁止在 CompletableFuture 或 @Async 异步线程里 acquire,再回主线程 release
- 所有 acquire() 后必须套 try-finally,release() 放在 finally 块里,防异常导致许可永久丢失
- 优先用 tryAcquire(long, TimeUnit),超时设 100~300ms,避免线程池被阻塞耗尽
限流点要落在真实耗资源处
别在 Controller 入口就拦。鉴权、参数校验、日志这些轻操作应放在 acquire 前;只有真正吃资源的动作——比如远程 HTTP 调用、DB 写入、大文件生成——才包进 acquire/release 临界区。
否则会出现“占着许可不干活”的空转现象:请求卡在非关键路径,却白白锁住并发额度,整体吞吐反而下降。
加个失败统计就能简易熔断
Semaphore 本身没熔断能力,但可以低成本叠加:
- 用 AtomicInteger 统计最近 60 秒失败请求数和总请求数
- 失败率超 50% 且总请求数 ≥20,开关置为 OPEN
- OPEN 状态直接拒绝新请求;30 秒后进 HALF-OPEN,只放行 1~2 个试探请求
- 试探成功则恢复,失败则重置计时器
这样就形成了“许可限流 + 失败熔断”的双控逻辑,在单机轻量场景下足够实用,也不用引入 Hystrix 等重型组件。











