Semaphore是Java并发包中用于实现固定并发数限流的信号量工具,通过acquire/release控制许可数量,需共享实例并配对调用以避免泄漏。

Semaphore 是 Java 并发包(java.util.concurrent)中一个轻量、高效的信号量工具,非常适合实现**固定并发数的限流器**——即限制同时最多只有 N 个线程能执行某段关键逻辑(比如调用下游接口、访问数据库连接池、处理上传任务等)。
核心原理:用许可(permits)控制准入
Semaphore 内部维护一组“许可”,构造时指定总数(如 new Semaphore(5) 表示最多 5 个许可)。线程通过 acquire() 尝试获取一个许可:有空闲就立即拿到并继续执行;没有就阻塞等待(或尝试非阻塞获取)。执行完后必须调用 release() 归还许可,否则许可会永久泄漏,导致后续线程永远卡住。
基础限流器写法(推荐 try-finally 保证释放)
以下是一个安全、可复用的限流模板:
<!-- 示例代码逻辑,非可运行完整类 -->
立即学习“Java免费学习笔记(深入)”;
步骤说明:
- 定义一个共享的
Semaphore实例(通常为static final或 Spring Bean 单例) - 在需要限流的业务逻辑前调用
semaphore.acquire() - 用
try-finally包裹业务代码,确保无论是否异常,都执行semaphore.release() - 若需支持超时控制,可用
acquire(long timeout, TimeUnit unit)避免无限等待
常见增强场景与建议
1. 支持公平性(避免线程饥饿)
构造时传入 true: new Semaphore(3, true)。开启公平模式后,等待队列按 FIFO 排序,先等的线程优先获取许可。默认是非公平的(性能略高,但可能个别线程长期抢不到)。
2. 动态调整并发数(慎用)
Semaphore 不提供直接修改许可总数的 API,但可通过 availablePermits() + release()/acquire() 组合模拟增减。更推荐的做法是:封装一层带原子更新的限流器类,内部用 AtomicInteger 管理目标并发数,并重建 Semaphore(注意旧实例上未 release 的线程需妥善处理)。
3. 与线程池配合使用
不要把 Semaphore 和线程池的 corePoolSize 混淆。Semaphore 控制的是「同一时刻执行某段逻辑的线程数」,而线程池控制的是「可创建/复用的工作线程总数」。二者可叠加:例如线程池有 20 个线程,但其中调用外部 HTTP 接口的部分被 Semaphore(3) 限流,最终效果就是最多 3 个请求并发发出。
避坑提醒
- 必须配对调用 acquire/release —— 忘记 release 是最常见 bug,会导致许可耗尽、系统假死
- 不要在 acquire 前做耗时操作(如查 DB、远程调用),否则会浪费许可等待时间
- 区分 permits 数量和业务含义:1 个 permit 不一定对应 1 次请求,可以按权重设计(如大文件上传占 2 个 permit,小图占 1 个)
- 注意作用域:如果每个请求新建一个 Semaphore,就完全失去限流意义;务必保证是跨请求共享的实例



















