semaphore仅控制瞬时并发数,不支持时间窗口限流;适用于单机临界资源保护,需确保释放进finally、使用带超时acquire、许可获取贴近真实耗时操作,且多实例需分布式方案替代。

Semaphore 本身不提供“防洪”能力,它只控制瞬时并发数,不是时间窗口内的请求数。想靠它实现“每秒最多100次”的限流,会失效;但它在单机临界资源保护上高效、轻量、零依赖。
明确适用边界:它控的是“同时跑几个”,不是“一秒钟来多少”
信号量本质是计数器型许可池,没有时间维度。比如 new Semaphore(5) 表示最多5个线程能同时进入临界区——不管这5个是1毫秒内进的,还是1分钟内陆续进的。它适合:
- 保护本地有限资源:数据库连接池、文件句柄、CPU密集型任务槽位
- 下游系统有明确并发上限(如老核心接口只支持8路并发)
- 后台定时任务导出、批量处理等低频但需防堆积的场景
- 单机部署、无横向扩缩容需求的服务
必须写对的三件事:释放、超时、作用域
生产环境出问题,90%源于这三点没落实:
- 释放必须进 finally:acquire() 后一旦业务抛异常,没 release() 就等于许可证永久丢失。正确写法是 try-finally 包裹业务逻辑,release() 放在 finally 块
-
Web 接口禁用无超时 acquire():阻塞式获取会让 Tomcat 线程卡住,拖垮整个容器。应改用
tryAcquire(1, 100, MILLISECONDS),超时直接返回 429 - 许可获取点要贴近真实耗时操作:不要在 Controller 入口就 acquire,而应在调用第三方 API 或执行 DB 查询前才拿许可——避免空转占坑
公平模式选非公平,除非你真需要排队顺序
默认构造 new Semaphore(10) 是非公平模式,允许后来线程插队抢到刚释放的许可,吞吐更高;公平模式(new Semaphore(10, true))强制 FIFO 队列,降低吞吐、增加延迟。除极少数要求严格请求顺序的审计类场景,其余都用非公平。
多实例部署?它天然不支持分布式
每个 JVM 实例持有一个独立 Semaphore 对象。3 台机器各配了 permits=10,实际总并发就是 30,完全突破预期。若需集群级限流,必须换方案:
- Redis + Lua 脚本(原子性保障)
- Sentinel 或 Nacos 配置中心统一阈值 + 本地缓存兜底
- 网关层统一限流(如 Spring Cloud Gateway 的 RequestRateLimiter)











