acquire()会阻塞直至获得许可或被中断,被中断时抛出interruptedexception且不获取许可,必须用try-catch处理并恢复中断状态;超时场景应使用tryacquire();acquireuninterruptibly()不抛异常但丢失中断信号;每次acquire后必须配对release。

acquire() 会阻塞直到拿到许可,除非被中断
Semaphore.acquire() 是一个可中断的阻塞调用。它会一直等待,直到当前线程获得一个许可,或者线程被其他线程调用 interrupt() 中断。一旦被中断,它会抛出 InterruptedException,且**不会获得许可**。
常见错误是忽略这个异常,或在 catch 块里不做清理 —— 结果是逻辑以为“拿到了许可”,实际根本没有,后续操作可能出错。
- 必须用
try-catch包裹,不能简单吞掉InterruptedException - 捕获后通常应恢复中断状态:
Thread.currentThread().interrupt() - 不要在 catch 里直接 return 或继续执行业务逻辑,除非你明确知道此时无许可也可进行
想避免无限阻塞?用 tryAcquire() + 超时控制
如果业务不能接受长时间等待(比如响应用户请求、定时任务),就别用 acquire(),改用带超时的 tryAcquire(long timeout, TimeUnit unit)。
它会在指定时间内尝试获取许可:成功返回 true;超时未获取到返回 false;等待中被中断则抛出 InterruptedException。
- 超时值不宜设为 0 ——
tryAcquire(0, TimeUnit.SECONDS)是非阻塞“试探”,几乎总失败,除非刚好有空闲许可 - 单位务必匹配,写成
tryAcquire(100, TimeUnit.MILLISECONDS)比tryAcquire(100)(即 100 纳秒)更安全 - 返回
false是正常流程分支,不是错误,应按业务降级处理(如返回限流响应、走异步队列等)
acquireUninterruptibly() 不抛中断异常,但会丢失中断信号
如果你确实需要“不响应中断”的语义(例如在 shutdown 阶段必须完成某资源清理),可用 acquireUninterruptibly()。
它会阻塞直到拿到许可,且**永远不会抛 InterruptedException**。但要注意:如果线程在等待时被中断,该中断状态会被内部清除,调用结束后 Thread.interrupted() 会返回 false。
- 仅适用于你完全掌控线程生命周期、且中断逻辑不依赖此 Semaphore 的场景
- 和
acquire()混用容易引发中断丢失问题,尤其在线程池中——建议整个模块统一风格 - 无法配合
Future.cancel(true)等标准中断机制工作
许可归还必须配对,漏掉 release() 会导致永久泄漏
每次成功 acquire()(或 tryAcquire() 返回 true)后,必须在对应作用域结束前调用 release(),否则许可数不会恢复,后续线程将永远阻塞或超时。
最稳妥的方式是用 try-finally:
semaphore.acquire();
try {
// 执行受保护的操作
} finally {
semaphore.release();
}
- 不要把
release()放在 try 块里 —— 异常可能跳过它 - 不要在 catch 块里条件性 release —— 只要 acquire 成功了,就必须释放
- 注意:
acquire(2)要配release(2),数量必须严格一致
acquire() 的阻塞行为不是“可选配置”,而是其语义核心;而开发时最容易犯的,是在没搞清线程是否可中断、是否允许超时的前提下,凭直觉选了某个重载方法。










