ReentrantLock 必须在 finally 块中释放,否则异常、提前 return 或中断会导致锁永久持有引发死锁;正确写法是 try-lock-finally + isHeldByCurrentThread() 防御判断。

ReentrantLock 在长事务中未在 finally 块中释放,极易引发线程阻塞甚至全局死锁,这不是偶发问题,而是资源管理缺失的必然结果。核心在于:锁的获取和释放必须严格配对,且释放动作必须在异常路径下依然执行。
为什么一定要在 finally 中 unlock
ReentrantLock 不像 synchronized 那样具备 JVM 层面的自动释放机制。一旦业务逻辑抛出异常、提前 return、或发生中断,而 unlock 被写在 try 块末尾或 if 分支里,锁就会永久被持有——当前线程挂了,锁却没还回去。
- 常见误写:
lock.lock(); doSomething(); lock.unlock();—— 中间任意一步异常,unlock 就跳过了 - 看似安全但仍有风险:
if (condition) { lock.unlock(); }—— 条件不满足时锁永远不释放 - 嵌套锁、重入场景下漏 unlock,会导致同一线程反复加锁后只释放部分次数,最终 blocked on lock
如何快速定位这类死锁
不要等线上雪崩才查。JVM 自带工具就能暴露问题:
-
jstack <pid>:搜索
parking to wait for <0x...>或java.util.concurrent.locks.ReentrantLock$NonfairSync,看哪些线程卡在 lock(),再往上追溯谁持有了该锁对象(注意 lock 对象的 hash 值) - JConsole / VisualVM 的「线程」页签:直接显示 BLOCKED 线程及等待的锁实例,点击可跳转到持有者线程栈
- 启用
-Djdk.internal.vm.debug=true(JDK17+)配合 jcmd,可输出更详细的锁持有链
正确写法与防御性习惯
标准模板不是“建议”,是强制规范:
立即学习“Java免费学习笔记(深入)”;
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
// 业务逻辑(可能抛异常、return、continue)
} finally {
if (lock.isHeldByCurrentThread()) { // 防御性判断,避免 unlock 未加锁的锁抛 IllegalMonitorStateException
lock.unlock();
}
}
- 永远不用
lock.tryLock()后省略 finally —— 即使成功获取,也必须确保释放 - 若使用
tryLock(timeout, unit),超时返回 false 时不能调用 unlock;但成功返回 true 时,仍需 finally 保障释放 - 避免在 lock 内部做耗时操作(如远程调用、大循环),否则即使正确释放,也会导致其他线程长时间等待,表现类似死锁
进阶防护:用 AOP 或 Wrapper 封装锁逻辑
人工写 try-finally 容易遗漏,尤其在多人协作或快速迭代中。可封装为工具方法:
public static <T> T withLock(ReentrantLock lock, Supplier<T> action) {
lock.lock();
try {
return action.get();
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}
}
调用:String result = withLock(myLock, () -> heavyCompute()); —— 锁生命周期完全由工具控制,业务代码零感知锁管理。
不复杂但容易忽略。真正稳定的长事务系统,不是靠人盯代码,而是靠结构化约束把错误可能性压到最低。


















