acquire() 会卡住而非报错,因 semaphore 默认阻塞等待许可;需配对使用 try-finally 确保 release(),避免泄漏;许可数应依真实资源瓶颈设定,不可随意拍脑袋。

为什么 acquire() 会卡住,而不是报错?
因为 Semaphore 的默认行为是「阻塞等待」——没有可用许可时,线程不会抛异常或返回失败,而是挂起,直到其他线程调用 release()。这和 tryAcquire() 的语义完全不同。
- 常见错误现象:
acquire()后日志突然断掉、线程池耗尽、接口超时,但没看到明显异常 - 根本原因:上游线程忘了
release(),或在异常路径中跳过了释放逻辑(比如try里 acquire,但catch里没 release) - 实操建议:永远把
acquire()放在try前,release()放进finally块,哪怕只有一行业务逻辑 - 公平性影响:构造
Semaphore(2, true)(true 表示公平模式)能避免饥饿,但会略微降低吞吐;默认非公平更高效,但可能让后到的线程“插队”抢到许可
tryAcquire() 和带超时的 tryAcquire(long, TimeUnit) 怎么选?
二者都用于避免无限阻塞,但适用场景差异很大:前者是「立刻决断」,后者是「给点时间试试」。
- 用
tryAcquire()的典型场景:做快速探针,比如限流兜底——拿不到许可就直接返回429 Too Many Requests,不等 - 用
tryAcquire(500, TimeUnit.MILLISECONDS)的典型场景:资源有短暂抖动(如下游 DB 连接池瞬时打满),允许等半秒再试,避免毛刺引发雪崩 - 注意陷阱:
tryAcquire()返回false时不抛异常,容易被忽略;务必显式判断并处理失败分支 - 性能提示:带超时的版本内部要注册定时任务,频繁调用会增加调度开销,别在高频循环里滥用
初始化 Semaphore 时,许可数设成多少才合理?
不是拍脑袋填个“10”或“100”,得结合资源瓶颈的真实容量来反推。
- 数据库连接池:如果 HikariCP 最大连接数是 20,那
Semaphore许可数就不该超过 20,否则会触发连接池拒绝,变成无效限流 - HTTP 客户端并发:若 OkHttp 的
ConnectionPool最大空闲连接是 5,同时路由并发上限是 10,那信号量取min(5,10)=5更稳妥 - 常见错误:把信号量当成“线程数限制”硬套 CPU 核心数(比如 8 核就设 8),但实际瓶颈常在 I/O 或外部依赖,和 CPU 关系不大
- 动态调整可能吗?
Semaphore本身不支持运行时改许可总数,但你可以封装一层,用AtomicInteger控制新申请是否放行,再配合定期重置Semaphore
和 ReentrantLock / CountDownLatch 混用时容易出什么问题?
它们看着都是“锁相关”,但语义完全不同:Semaphore 是资源配额,ReentrantLock 是互斥临界区,CountDownLatch 是倒计时门栓——混用等于拿扳手拧螺丝。
- 典型误用:用
Semaphore替代ReentrantLock做临界区保护,结果多个线程同时进了同一段代码,因为许可数 > 1 - 典型误用:用
CountDownLatch等待所有线程完成,却忘了每个线程还要acquire()/release(),导致许可泄漏 + 等待永不结束 - 关键区别记法:
acquire()不绑定线程身份,谁拿到谁用;而lock()必须由同一线程unlock(),否则抛IllegalMonitorStateException - 真实调试线索:如果发现
availablePermits()持续为 0 且不再恢复,大概率是某处acquire()和release()不成对,用jstack查阻塞线程堆栈最直接








