new semaphore(1) 不等于 synchronized,因其不绑定线程、不可重入、无内存可见性保证,且需手动配对acquire/release;适合跨线程传递许可,而非简单互斥。

为什么 new Semaphore(1) 不等于 synchronized
很多人以为 Semaphore 设为 1 就是“换个写法的锁”,其实不是。它不绑定线程——同一个线程可以 acquire() 多次(不释放的话会阻塞自己),而 synchronized 是可重入且自动绑定当前线程的。更关键的是:Semaphore 不提供内存可见性保证,除非你额外用 volatile 或同步块配合共享变量。
- 要用
Semaphore做互斥,得确保每次acquire()后必有对应release(),推荐用try-finally - 如果只是保护一段临界区且无需跨线程传递许可,
synchronized或ReentrantLock更安全、语义更清晰 -
Semaphore(1)真正适合的场景是:需要在不同线程间“传递”进入权限(比如生产者获取许可后交给消费者去执行)
如何避免 acquire() 阻塞导致线程卡死
默认 acquire() 会无限期等待,一旦许可长期被占用(比如某线程抛异常没释放),整个等待队列就挂住了。必须用带超时的版本 + 显式错误处理。
- 优先用
tryAcquire(long timeout, TimeUnit unit),超时返回false,可做降级或告警 - 不要在循环里裸调
acquire(),尤其在连接池、任务调度等长生命周期场景中 - 如果业务允许“尽力而为”,可结合
tryAcquire()(无等待)快速失败,避免排队积压
if (sem.tryAcquire(3, TimeUnit.SECONDS)) {
try {
// 执行受控操作
} finally {
sem.release(); // 必须放 finally 里
}
} else {
// 走备用逻辑,比如返回错误码或进重试队列
}
公平模式(fair = true)带来的性能代价
new Semaphore(n, true) 会让等待线程按 FIFO 排队,看起来更“可控”,但实际会显著降低吞吐量——因为要维护队列结构、增加 CAS 竞争,尤其在高并发下,平均响应时间可能翻倍。
- 仅当业务强依赖“先到先得”语义时才开启公平模式(例如某些金融限流策略)
- 绝大多数限流、资源池场景(如数据库连接数控制),用非公平模式(默认)更合适
- 公平模式下
acquire()的延迟波动更大,监控时容易误判为系统抖动
release() 被多调用一次会发生什么
Semaphore 允许 release() 次数超过初始许可数,这会导致许可数“虚高”。比如初始为 5,被多 release() 两次后变成 7,后续 7 个线程都能直接通过 —— 这不是 bug,是设计使然,但极易引发资源超卖。
立即学习“Java免费学习笔记(深入)”;
- 典型诱因:异常分支漏写
acquire(),但release()却执行了(比如在 finally 里无条件 release) - 解决方法:把
acquire()和release()绑定在同一作用域,或用封装类(如AutoCloseableSemaphore)强制配对 - 没有运行时检查机制,只能靠代码审查和单元测试覆盖边界路径
release(),少一次 acquire(),系统照常跑,直到流量高峰时连接被打满、线程池饿死,才暴露出来。










