reentrantlock 调试难点在于线程 dump 无法直观显示锁状态,但 getqueuedthreads() 和 getholdcount() 可有效定位死锁、锁泄漏及重入异常:前者返回等待队列线程快照,后者返回当前线程重入次数,需持有锁才有效,二者组合分析并配合监控可提升诊断效率。

ReentrantLock 的调试难点在于无法像 synchronized 那样通过线程 dump 直观看到锁持有者和等待队列。但 getQueuedThreads() 和 getHoldCount() 是两个被低估却极其实用的诊断工具,合理使用能快速定位死锁、锁泄漏或重入异常。
查看当前等待锁的线程列表
getQueuedThreads() 返回一个按 FIFO 顺序排列的 List<thread></thread>,包含所有已调用 lock() 但尚未获取到锁的线程(即处于 AQS 同步队列中)。它不包含因条件变量(Condition.await())而阻塞的线程。
- 在关键临界区入口或超时获取锁失败后立即调用,可确认是否有线程长期排队
- 结合
Thread.getName()和Thread.getState()判断是否卡在 BLOCKED 状态 - 注意:该方法返回的是快照,多线程环境下结果可能瞬时变化,建议配合日志或 JMX 暴露为监控指标
确认当前线程对锁的重入深度
getHoldCount() 是实例方法,必须由**持有该锁的线程**调用才有效,返回当前线程对该锁的重入次数。若当前线程未持有锁,返回 0。
- 在进入和退出嵌套锁逻辑前后打印此值,验证重入是否符合预期(如递归调用、回调场景)
- 若业务逻辑应为“非重入”,但该值 > 1,说明存在意外的重复 lock() 调用,可能导致 unlock() 不匹配
- 与
isHeldByCurrentThread()联用,可构建更健壮的断言:例如assert lock.isHeldByCurrentThread() && lock.getHoldCount() == 1;
组合使用定位典型问题
单独看某个方法信息有限,联合分析才能揭示真实状态:
-
疑似死锁:某线程在
getQueuedThreads()中持续出现,且其堆栈显示正等待另一个 ReentrantLock;同时检查它自己持有的锁(通过getHoldCount()> 0)是否被他人排队等待 -
锁泄漏:服务运行一段时间后,
getQueuedThreads().size()持续增长,而活跃线程数稳定 → 可能有 lock() 后未 unlock()(尤其在异常分支) -
误用可重入性:日志中发现某线程
getHoldCount()达到 5+ 且无明确递归逻辑 → 检查是否在循环或监听器中反复调用了 lock()
实际调试建议
不要只在出问题时手动加日志。推荐以下轻量接入方式:
- 将
lock.getQueueLength()(返回等待线程数量,比 getQueuedThreads() 开销小)作为 Micrometer 指标定时上报 - 在统一的锁模板方法(如
withLock(Runnable))中,开启 debug 日志时自动记录getHoldCount()和排队长度 - JVM 启动时添加
-Djdk.locks.friendly=true(Java 19+),增强 jstack 对 ReentrantLock 的识别能力,使线程 dump 中出现 “waiting for lock” 提示










