semaphore.acquire() 会阻塞线程,许可不足时线程挂起进入aqs同步队列等待;默认非公平模式可能插队,公平模式需显式指定true;务必配对使用acquire/release且release置于finally中。

Semaphore.acquire() 会阻塞线程吗
会,但取决于构造方式。默认 Semaphore(permits) 是非公平模式,acquire() 在许可不足时直接进入 AQS 同步队列等待,线程挂起,不占用 CPU。
常见误判是以为“只是轮询”,实际是真正的阻塞式等待。如果想避免阻塞,得用 tryAcquire() 或带超时的 tryAcquire(long timeout, TimeUnit unit)。
- 非公平模式(默认):新请求可能插队,吞吐略高,但可能饿死老线程
- 公平模式:
new Semaphore(permits, true),严格 FIFO,响应更可预测,但性能稍低 - 注意:
acquireUninterruptibly()不响应中断,适合必须拿到许可的场景,但要小心死锁风险
如何用 Semaphore 控制数据库连接数
典型场景:限制应用最多同时打开 10 个数据库连接,避免连接池耗尽或 DB 端限流。关键不是替代 HikariCP 这类连接池,而是做**跨服务/跨模块的粗粒度协调**。
示例逻辑:在获取连接前先 semaphore.acquire(),用完后在 finally 块里 semaphore.release()。漏掉 release() 会导致许可永久泄漏。
立即学习“Java免费学习笔记(深入)”;
- 务必把
release()放在finally中,哪怕发生 SQL 异常或网络超时 - 不要在连接对象上做
close()后再release()—— 如果 close 抛异常,release 就跳过了 - 许可数建议略小于连接池最大值(比如池设 20,Semaphore 设 15),留出管理连接余量
release() 调用次数多于 acquire() 会怎样
不会报错,Semaphore 允许“超额释放”,即许可计数可为负数。这本身不是 bug,而是设计特性 —— 它让初始化阶段“预占”许可、或补偿性释放成为可能。
但绝大多数业务场景下,负许可意味着逻辑错误:比如重复 release、或在未 acquire 的线程里调用了 release。JVM 不拦截,但后续 acquire() 会立刻成功(因为 count ≥ 0),掩盖资源竞争问题。
- 调试时可临时包装一层:记录每次 acquire/release 的线程栈,用
Thread.currentThread().getStackTrace() - 测试阶段开启 JMH 或 Arthas watch,监控
Semaphore.availablePermits()是否异常波动 - 静态检查工具(如 ErrorProne)有
GuardedBy和ThreadSafety规则,能捕获部分不匹配调用
和 ReentrantLock 比,Semaphore 适合什么场景
ReentrantLock 是“单线程独占 + 可重入”,Semaphore 是“N 线程共享 + 无所有权”。选错会导致语义混乱:比如用 Semaphore 做临界区互斥(该用 Lock),或用 Lock 控制 5 个 API 并发调用(该用 Semaphore)。
- 用 Semaphore:限流(QPS/连接数/文件句柄)、生产者-消费者数量配比、分批次任务调度
- 用 ReentrantLock:保护一个 sharedList 的 add/remove、确保某个配置只被初始化一次
- 别混用:不要在同一个临界资源上既用 lock.lock() 又用 semaphore.acquire(),二者同步语义不兼容
真正容易被忽略的是许可的“语义归属”——Semaphore 不绑定线程,acquire() 和 release() 可以跨线程调用。这点既带来灵活性,也提高出错概率。写的时候多问一句:这个 release,真的对应前面那个 acquire 吗?










