核心是切断“连接持有”与“线程挂起”的耦合:wait()不释放connection,导致池枯竭;所有阻塞操作须在获取连接前或关闭后执行;连接池需配置超时、泄漏检测等强制回收机制;禁用无超时wait(),改用带超时等待或异步方案;通过指标监控和jstack巡检主动识别卡住连接。

核心在于切断“连接持有”与“线程挂起”的耦合。Object.wait() 本身不释放连接,只要线程还持有 Connection 对象(哪怕只是引用未 close),连接就仍在被占用;而 wait() 又让线程停在池中不归还、不超时、不主动释放,最终池迅速枯竭。
严格隔离阻塞操作与连接生命周期
任何可能触发 wait() 的逻辑——比如手动调用 Object.wait()、使用 CountDownLatch.await()、Semaphore.acquire()、Future.get()(未设超时)、或第三方 SDK 内部的同步等待——都必须发生在获取数据库连接之前,或在连接已明确关闭之后。
- 错误示例:在 try-with-resources 块内调用 latch.await(),此时 Connection 尚未关闭,线程 suspend,连接卡死
- 正确做法:先完成所有协调类等待,再打开连接执行 SQL;或把等待逻辑抽到 service 层前置,DAO 层只做纯粹的数据库交互
连接池配置必须启用强制回收机制
不能依赖应用层“自觉归还”。需通过连接池参数兜底,确保即使线程 wait 住,连接也能被强制回收并复用:
- connection-timeout(如 HikariCP 的 connection-timeout):设为合理值(如 5–10 秒),限制从池中借连接的最大等待时间,避免新请求无限排队
- leak-detection-threshold(如 HikariCP):设为略大于业务最长 SQL 执行时间(如 60 秒),一旦连接被借出超时未归还,池自动记录警告并尝试回收(部分池支持 force-close)
- max-lifetime 和 idle-timeout 需协同设置:避免因连接过早失效引发重连风暴,但也不能过长导致“僵尸连接”长期占位
禁用无保护的原始 wait()/notify() 模式
业务代码中直接调用 obj.wait() 是高危行为,既无超时保障,也无中断响应,极易造成不可控挂起。应统一替换为:
- 带超时的显式等待:CountDownLatch.await(3, TimeUnit.SECONDS)、Lock.tryLock(2, TimeUnit.SECONDS)
- 基于 CompletableFuture 的异步编排:避免线程阻塞,自然规避 wait 场景
- 若必须用 wait,务必包裹在 try-catch-InterruptedException 中,并在 catch 块里立即 close 当前持有的 Connection(如有)
监控与快速识别隐患点
靠日志和告警被动响应太慢。应在运行时建立两层探测:
- 连接池指标看板:实时跟踪 activeConnections / idleConnections / pendingAcquires,当 pendingAcquires 持续 > 0 且 active 稳定在 max,说明有连接被“卡住”
- jstack 自动巡检:定时抓取线程栈,过滤含 “java.lang.Object.wait” 且堆栈中出现数据库操作类(如 PreparedStatement、Connection)的线程,定位具体业务方法











