reentrantlock必须在finally中unlock以防止锁泄漏,因try-catch无法覆盖所有退出路径;标准写法是lock()在try外、unlock()在finally内,并建议加isheldbycurrentthread()校验。

ReentrantLock这类显式锁不会自动释放,一旦在临界区抛出异常、提前return或发生中断,而unlock又没放在finally里,锁就永远被占着——其他线程会在lock()上无限等待,CPU占用低但业务彻底卡死,这种现象就是锁泄漏。
为什么必须把unlock放进finally
try-catch本身无法覆盖所有退出路径:try块末尾有return会直接跳出;catch里再抛异常会让后续代码跳过;break、continue甚至JVM异常(如OutOfMemoryError)都可能绕过普通释放逻辑。只有finally能100%保证执行。
- lock()成功后,无论业务逻辑是否抛异常、是否提前返回,finally里的unlock都会被执行
- 即使try块中发生除零、空指针、IO失败等任意异常,锁也能及时归还
- 这是JUC锁与synchronized最核心的区别:后者由JVM保障自动释放,前者全靠程序员守规矩
标准写法:lock在try外,unlock在finally内
正确结构是先调用lock(),再进try-finally,而不是把lock()也包进try里——因为加锁失败(比如被中断)属于获取阶段的问题,和“持有后未释放”不是一回事。我们关注的是:只要拿到了锁,就必须还。
- lock.lock();
- try { /* 可能抛异常的业务代码 */ }
- finally { lock.unlock(); }
更稳妥的做法:加持有状态检查
如果lock()调用本身因中断失败(例如用了lockInterruptibly()并被interrupt()),当前线程并未真正持有锁,此时直接unlock()会抛IllegalMonitorStateException。所以建议在finally中加一层判断:
- finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }
- 这个检查能避免非法unlock,尤其适合在复杂流程或可中断场景中使用
- 注意:isHeldByCurrentThread()开销极小,不影响性能
别踩这些常见坑
很多问题看似隐蔽,实则源于对锁生命周期理解偏差:
- 在catch里unlock——异常时可能根本没拿到锁,强行unlock报错
- 把unlock写在if/else分支里——遗漏某条路径就会漏释放
- 重入次数不匹配——同一线程lock()三次,却只unlock()两次,锁实际未释放
- 锁对象为null时调用unlock()——初始化遗漏或构造时机错误,导致NPE
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











