Semaphore 是并发数守门人,通过许可数限制瞬时并发深度而非请求频率;需配合超时、降级、可靠释放及合理容量配置,非公平模式更适高频场景。

Java 中的 Semaphore 在高频资源请求场景下,不是直接的“限频”工具,而是关键的并发数守门人。它不按时间窗口(如每秒多少次)控制请求,而是通过许可数量硬性限制同一时刻能真正触达下游资源的线程数,从而防止资源被瞬间压垮——这是它在高频请求中不可替代的保护作用。
它真正守住的是“并发深度”,不是“请求频率”
高频请求常伴随大量线程排队、连接堆积、响应延迟飙升等问题。Semaphore 的核心价值在于:
- 把“请求洪峰”转化为可控的“执行队列”;
- 避免下游(如数据库连接池、第三方 API、本地文件句柄)因瞬时并发超载而拒绝服务或崩溃;
- 为系统争取缓冲时间,让降级、重试、熔断等机制有机会生效。
例如:一个每秒收到 200 次调用的接口,下游数据库连接池最大仅支持 10 个活跃连接。若不做控制,90% 的线程会卡在 getConnection() 上,最终触发连接超时、线程池耗尽、整个服务雪崩。而 new Semaphore(10) 能确保最多只有 10 个线程真正发起 DB 操作,其余 190 个请求要么等待、要么被超时拦截——把压力挡在临界区入口。
必须配合超时与降级,否则反而成隐患
acquire() 无超时会无限阻塞,高频场景下极易拖垮线程池。正确做法是:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 使用
tryAcquire(long timeout, TimeUnit unit),超时值设为下游 P95 响应时间的 1.5~2 倍(如 Redis P95 是 80ms,超时设 120–160ms); - 超时后主动降级:返回缓存、抛业务异常、记录告警,绝不让线程空等;
- 禁用
acquireUninterruptibly(),避免 JVM 关闭时 hang 住。
许可数必须基于真实容量,不能拍脑袋
- 数据库连接池最大连接数 = 20 →
Semaphore(20),加 10% 余量 →Semaphore(22); - 第三方 API 明确限流 “5 QPS”,单靠
Semaphore(5)不够——需搭配ScheduledExecutorService每 200ms 放行 1 个请求,再用Semaphore(1)防突发并发; - 若下游自动扩缩容(如云数据库),
Semaphore会失效,此时应切换为Resilience4j RateLimiter等支持滑动窗口的限流器。
释放必须绝对可靠,否则系统静默假死
许可泄露是高频服务中最隐蔽的故障源:
-
acquire()后必须立刻进try块,release()必须写在finally中; - 禁止在
if分支里释放,禁止跨线程释放(哪怕语法合法); - 同一线程多次
acquire(n),必须对应release(n); - 可用
availablePermits()监控,持续为 0 且线程 WAITING,大概率已泄露。
非公平模式更适合高频吞吐场景
new Semaphore(5, true)(公平)看似稳妥,实则带来约 20%+ 性能损耗,且无法根治饥饿。默认非公平模式允许刚释放的许可被新线程快速抢到,提升整体吞吐。仅当监控明确发现某类请求长期等待(如某线程 ID 累计等待 >5s),才考虑启用公平模式。
不复杂但容易忽略

















