必须显式设置acquire()超时,统一用tryAcquire(long,TimeUnit)并合理配置超时值;Semaphore构造需按场景选公平模式;release()须在finally中紧随idleConnections.offer()调用;许可与连接对象生命周期解耦,不可混淆。

acquire() 超时设置必须显式指定,否则阻塞不可控
默认 acquire() 会无限期等待许可,一旦连接池长期满载或归还延迟,调用线程就会卡死。生产环境必须用带超时的重载方法,比如 acquire(1000) 或 tryAcquire(1, TimeUnit.SECONDS)。
常见错误是只写 acquire(),结果压测时大量线程堆积在获取许可阶段,监控看不出来连接池本身问题,但整体响应时间飙升。
- 建议统一使用
tryAcquire(long timeout, TimeUnit unit),失败时抛出业务异常或走降级逻辑 - 超时值不宜过短(如 10ms),要略大于连接实际空闲平均等待时间,可通过日志统计
idleConnections队列长度和出队耗时来估算 - 不要依赖
acquireUninterruptibly(),它会忽略中断信号,导致线程无法被优雅关闭
公平性参数 true/false 直接影响线程排队行为与吞吐量
构造 Semaphore 时第二个布尔参数决定是否启用 FIFO 排队:new Semaphore(maxSize, true) 是公平模式,false 或省略则为非公平模式。
非公平模式下,刚释放许可的瞬间,新来的线程可能直接抢到,而队头等待已久的线程继续挂起——这在高并发短任务场景下吞吐更高;但若存在长耗时操作(如慢 SQL),就容易造成后者饥饿。
- 金融类系统、订单扣减等强顺序敏感场景,必须设为
true - 后台批处理、日志上报等对顺序无要求的任务,用默认
false更稳 - 公平模式下,每次
release()后只唤醒队首一个线程;非公平模式可能唤醒多个,再由它们竞争,所以调度开销更低
release() 必须放在 finally 块里,且不能和连接归还逻辑耦合
release() 归还的是“许可”,不是连接对象本身。很多开发者误以为把连接放回 idleConnections 队列就算归还完成,忘了调用 release(),导致许可数永久减少,池逐渐“锁死”。
典型错误写法:idleConnections.offer(conn); // 忘了 release()。现象是池最大容量为 10,但跑一会儿只剩 2 个许可可用,且无法恢复。
- 标准结构只能是:先
idleConnections.offer(conn),再semaphore.release() - 两者必须包裹在同一个
try-finally中,哪怕连接close()抛异常也不能跳过release() - 禁止在连接对象上做任何状态判断后再决定是否
release(),许可归还和连接健康度无关
许可数 ≠ 连接总数,空闲队列才是真实资源载体
Semaphore 的许可只控制“正在使用的连接数上限”,不管理连接生命周期。真正存连接的是你维护的 idleConnections(如 ConcurrentLinkedQueue 或 BlockingQueue)。
这意味着:即使许可数设为 10,如果 idleConnections 里只有 3 个连接,那最多只有 3 个线程能拿到连接——其余 7 个线程虽然拿到了许可,却取不到连接实例,只能自己处理空指针或重试逻辑。
- 预热阶段必须主动创建并
offer()到空闲队列,不能只靠首次请求触发 - 连接失效检测(如 ping 失败)应从
idleConnections中移除,并同步semaphore.release()补回许可 - 不要用
semaphore.availablePermits()判断池是否“有空闲连接”,它只反映剩余许可数,和实际连接是否存在无关

















