Semaphore通过许可计数控制并发线程数,初始化指定最大许可数(如new Semaphore(5)),支持公平/非公平模式;需共享同一实例,用acquire()获取、release()释放许可,常用于限流场景。

Java 中的 Semaphore 通过“许可计数”机制来控制并发访问资源的线程数量,它不锁定资源本身,而是发放有限数量的“入场券”,只有拿到券的线程才能执行受保护的操作。
核心逻辑很简单:有多少张许可,最多就允许多少个线程同时进入。
初始化时设定最大并发数
创建 Semaphore 实例时传入一个整数,表示初始可用许可总数,也就是你允许的最大并发线程数:
Semaphore semaphore = new Semaphore(5); // 最多 5 个线程可同时访问
这个值就是硬性上限。你可以把它理解为数据库连接池大小、API 每秒调用配额、打印机任务槽位等实际资源的抽象。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 构造方法支持第二个参数指定是否公平:
new Semaphore(5, true)表示按等待顺序分配许可(FIFO),默认是非公平模式(性能更好,多数场景推荐)。
用 acquire() 获取许可,用 release() 归还
每个线程在进入临界区前必须调用 acquire(),成功后许可数减 1;执行完必须在 finally 块中调用 release(),让许可数加 1:
try {
semaphore.acquire(); // 阻塞直到拿到许可
// 执行受限操作:如调用外部接口、写文件、访问慢资源
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 必须释放!否则许可永久丢失
}-
acquire()会阻塞,直到有许可可用; -
tryAcquire(long timeout, TimeUnit unit)更适合生产环境,避免无限等待; -
release()不要求是同一个线程调用,也不检查调用者身份,只要释放就行; - 可批量操作:
acquire(2)、release(3),适用于一次需要多个资源单位的场景。
确保全局共享同一个 Semaphore 实例
如果每次都在方法内新建 Semaphore,那就完全失效了。必须保证所有线程竞争的是同一个实例:
- 声明为
static final(工具类中); - 或作为 Spring Bean 管理(Web 应用中);
- 或通过依赖注入方式统一提供。
否则各用各的信号量,起不到限流作用。
典型适用与避坑提醒
- 适合场景:数据库连接池上限、硬件设备(串口/打印机)并发控制、第三方 API 调用配额、文件句柄复用;
-
不适合场景:保护单个变量或简单代码块的互斥——此时用
synchronized或ReentrantLock更轻量; -
常见错误:
- 忘记
release()或异常未进finally,导致许可泄漏; - 在
acquire()前做耗时操作(如查 DB、远程调用),造成许可被占着却没干活; - 把
Semaphore和大线程池混用却不调整许可数,误以为线程数 = 并发数。
- 忘记
不复杂但容易忽略。

















