Semaphore适用于单机高并发限流,核心是控制同一时刻最多N个线程执行耗资源操作;它不支持跨实例、时间窗口或总请求数限制,必须严格配对获取与释放,推荐使用带超时的tryAcquire并配合简易熔断提升健壮性。

Java 中 Semaphore 实现高并发限流,核心是用许可数控制“同一时刻最多几个线程能执行耗资源操作”,不是控总请求数、也不跨实例,适合单机轻量场景。
明确它能做什么、不能做什么
Semaphore 本质是 JVM 内的计数器:new Semaphore(5) 表示最多 5 个线程能同时进入临界区——不管这 5 个是 1 毫秒内进的,还是 1 分钟内陆续进的。它只管“并发数”,不管“频次”“时间窗口”“集群总量”。
- ✅ 适合:调用强依赖第三方(如银行/保险核心系统,明确要求单机 ≤5 路)、后台导出任务、内部管理接口等低 QPS、无水平扩缩容需求的场景
- ❌ 不适合:Web 公共接口限流(多实例部署时每台都独立计数,总并发 = 单机限制 × 实例数)、需要“每分钟 100 次”这类滑动窗口控制、或对熔断/自适应有完整要求的系统
关键三步:初始化、获取、释放
必须严格配对,且释放动作绝不能被异常跳过。
- 全局唯一实例:Spring 环境下声明为 @Bean;禁止每次 new,否则每个对象都是独立计数器
- 获取许可放真正耗资源前:比如在发起 HTTP 调用或执行 DB 写入之前调用 tryAcquire(),而不是 Controller 入口就拦——避免鉴权、日志等轻量逻辑“空转占坑”
- 释放必须进 finally 块:acquire() 或 tryAcquire() 成功后,release() 一定要写在 finally 里,否则业务异常或超时中断会导致许可永久泄漏,后续请求全被阻塞
生产环境推荐用法:带超时的 tryAcquire
阻塞式 acquire() 会让 Tomcat 或 Netty 线程挂起,极易耗尽线程池,造成整体响应变慢甚至 503。应优先使用带超时的非阻塞方式:
立即学习“Java免费学习笔记(深入)”;
- 用 semaphore.tryAcquire(1, 100, TimeUnit.MILLISECONDS):等待最多 100ms,超时直接返回 false
- 获取失败时,快速返回 HTTP 429 Too Many Requests,而非让用户干等
- 避免用无参 tryAcquire():它不等、抢不到就 false,对瞬时毛刺太敏感,容易误伤正常流量
叠加简易熔断提升健壮性
Semaphore 本身不熔断,但可配合原子计数器模拟基础熔断逻辑:
- 用 AtomicInteger 统计最近 60 秒内的失败请求数和总请求数
- 失败率超 50% 且总请求数 ≥20,触发 OPEN 状态
- OPEN 状态下跳过 tryAcquire,直接拒绝所有请求,30 秒后进入 HALF-OPEN,只放行 1~2 个试探请求
- 试探成功则恢复服务;失败则重置计时器


















