semaphore适用于“最多n个并发”的限流场景,语义清晰;必须用try-finally确保release()执行以防许可泄露;acquire()需处理interruptedexception;按需选用阻塞/非阻塞获取方式;公平性依场景选择,资源池类通常用默认非公平模式。

明确语义:用Semaphore代替锁做限流
Semaphore不是用来互斥的,而是用来表达“最多N个并发”的。比如数据库连接池设为5,就直接写new Semaphore(5),语义清晰、不易误用。synchronized或ReentrantLock做不到这点——它们默认只放行1个线程,强行模拟多并发容易出错,还可能因漏释放导致死锁。
必须配try-finally:防许可泄露是生死线
acquire()后不保证一定执行release(),一旦业务抛异常且release写在try块末尾或catch里,许可证就永久丢失。10个线程跑几轮后,所有后续请求都会卡在acquire()上不动。
- ✅ 正确姿势:release()必须放在finally中,无论成功、中断还是RuntimeException都兜得住
- ❌ 错误示范:把release()写在try最后,或仅在catch里调用
- ⚠️ 注意:acquire()会抛InterruptedException,需捕获并恢复中断状态(Thread.currentThread().interrupt())
按需选阻塞或非阻塞获取方式
普通场景用acquire()就行,它会一直等,适合对响应时间不敏感但要求强限流的业务(如后台批量任务)。如果接口需要快速失败或降级,就用tryAcquire()。
- tryAcquire():立即返回true/false,适合熔断判断
- tryAcquire(long timeout, TimeUnit unit):带超时,比如等2秒拿不到许可就走降级逻辑
- 别混用:acquire()和tryAcquire()不能交叉管理同一组许可,语义不一致易混乱
公平性要考虑真实场景
默认是非公平模式,新线程可能插队抢到许可,吞吐高但等待时间不可控;设成公平(new Semaphore(3, true))后,线程按FIFO排队,适合对响应时间一致性要求高的服务,比如支付网关。
- 公平模式会略微降低吞吐量,但避免“长尾等待”问题
- 秒杀类场景慎用公平模式——高并发下排队队列本身可能成瓶颈
- 资源池类场景(如DB连接、HTTP客户端)通常用默认非公平即可











