Semaphore是Java并发包中用于控制同时访问资源的线程数量的核心限流工具,通过许可(permits)机制实现acquire/release配对操作,支持动态调整、公平/非公平模式及定时重置策略。

Semaphore 是 Java 并发包(java.util.concurrent)中用于控制**同时访问特定资源的线程数量**的核心工具,非常适合在高并发场景下实现轻量、灵活的限流控制——它不依赖外部组件,响应快,且可精确到“许可数”级别。
理解 Semaphore 的核心机制
Semaphore 维护一组“许可”(permits),线程需先 acquire() 获取许可才能执行受保护逻辑;执行完后必须调用 release() 归还许可。若当前无可用许可,acquire() 默认阻塞,也可选择超时或非阻塞尝试。
- 构造时指定总许可数,例如
new Semaphore(10)表示最多 10 个线程可并发执行 - 支持公平模式(按等待顺序分配许可),但默认是非公平的,吞吐更高
- 许可数可动态调整:
release(n)增加许可,drainPermits()清空当前所有许可
典型限流场景:接口 QPS 限制(每秒最多 100 次请求)
注意:Semaphore 本身不带时间窗口,要实现“每秒限流”,需配合定时重置策略(如 ScheduledExecutorService):
- 初始化一个
Semaphore(100) - 每秒执行一次
semaphore.drainPermits(); semaphore.release(100);—— 相当于每秒重置 100 个新许可 - 每个请求调用
semaphore.tryAcquire(),成功则处理,失败则快速返回 429
⚠️ 这种方式简单高效,适合对精度要求不苛刻(如允许短时略超 100)、且 QPS 稳定的场景。若需严格滑动窗口(如 Redis + Lua),应结合外部存储。
立即学习“Java免费学习笔记(深入)”;
实战建议:避免常见陷阱
- 务必配对使用 acquire / release:推荐用 try-finally 或 try-with-resources(配合自定义封装类)确保释放,否则许可泄露会导致服务永久不可用
- 慎用
acquireUninterruptibly():会忽略线程中断信号,影响服务优雅停机 - 不要用 Semaphore 替代 synchronized 或 ReentrantLock:它不保证内存可见性与临界区互斥语义,仅作“准入控制”
- 监控很重要:可通过
getQueueLength()和availablePermits()暴露指标,接入 Prometheus 实时观察排队长度和许可余量
一个简洁可靠的限流模板
以下是一个线程安全、自动清理、支持超时的工具类片段:
public class RateLimiter {
private final Semaphore semaphore;
private final long timeoutMs;
public RateLimiter(int permits, long timeoutMs) {
this.semaphore = new Semaphore(permits);
this.timeoutMs = timeoutMs;
}
public boolean tryAcquire() {
try {
return semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public void release() {
semaphore.release();
}
}
在 Spring WebMvc 中,可将其注入拦截器或 @Aspect 切面,在 controller 方法前统一校验;在 Spring WebFlux 中需配合 Mono.defer 转为响应式调用。



















