Semaphore通过固定数量许可控制并发,初始化指定上限,需全局唯一实例;acquire/release必须成对且置于try-finally中;推荐使用带超时的tryAcquire避免阻塞;适用于资源配额场景,非互斥保护。

Java 中 Semaphore 控制并发线程数量,核心是“发固定数量的入场券”,谁拿到券谁干活,用完必须还——券总数就是最大并发数。
初始化时定好最大并发数
构造 Semaphore 时传入整数,就是允许同时访问资源的线程上限:
- new Semaphore(5) 表示最多 5 个线程能并行执行受保护代码
- 默认是非公平模式,性能更好;只有明确需要 FIFO 调度(如强顺序扣库存)才加 true 启用公平模式
- 务必保证全局共用同一个实例(如 static final 或 Spring 管理的 Bean),否则限流失效
acquire() 和 release() 必须成对且带兜底
获取许可进临界区,释放许可让别人进来。关键不是“写了这两行”,而是“任何路径都不能漏掉 release”:
- 一定要放在 try-finally 块里:即使业务逻辑抛异常、return 提前退出,也必须归还许可
- 错误写法:只在 try 块末尾 release —— 一旦中间抛 NullPointerException 或 IO 异常,许可就永久丢失,后续线程全卡死
- 正确写法:acquire() 在 try 外或 try 内开头,release() 固定写在 finally 里
生产环境推荐用带超时的获取方式
避免线程无限等待导致服务雪崩:
- 别用无参 acquire(),它可能永远阻塞
- 改用 tryAcquire(long timeout, TimeUnit unit):超时返回 false,可走降级逻辑(如返回缓存、记录告警、快速失败)
- 若被中断,捕获 InterruptedException 后要调用 Thread.currentThread().interrupt() 恢复中断状态
适用场景和常见误用提醒
Semaphore 不是万能锁,它解决的是「N 选 M」问题,不是「互斥」问题:
- 适合:数据库连接池上限、打印机/串口等硬件并发控制、第三方 API 每秒调用配额、文件句柄复用
- 不适合:保护单个变量或简单代码块的读写安全(此时用 ReentrantLock 或 synchronized 更轻量)
- 警惕:不要在 acquire() 前做耗时操作(如远程调用、查 DB),否则券被占着却没干活,白白浪费并发能力
- 注意:线程池大小 ≠ 并发数。10 个线程的池配 Semaphore(2),实际并发仍只有 2
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











