ReentrantLock 必须在 try 块外调用 lock(),unlock() 必须在 finally 中执行,否则会导致锁泄漏;需严格匹配加锁/解锁次数,公平性设置不影响此规则。

lock() 必须在 try 块外调用,且 unlock() 必须在 finally 中执行
这是最常出错的环节:把 lock() 放进 try 块里,或者漏掉 finally,都会导致锁泄漏。一旦某个线程因异常没走到 unlock(),后续所有线程就永远卡在 lock() 上。
-
lock()调用本身可能不会抛异常,但它之后、try之前若插入日志、参数校验等代码,而这些代码抛了异常,unlock()就根本不会执行 -
unlock()一定要写在finally里,哪怕临界区只有一行赋值也要套上try-finally - 不要在
catch里调用unlock()—— 异常可能发生在加锁前,此时调用会抛IllegalMonitorStateException
正确写法:
lock.lock();
try {
// 临界区逻辑
sharedValue++;
} finally {
lock.unlock();
}
为什么不能用 try-with-resources 或 catch 代替 finally
ReentrantLock 不实现 AutoCloseable,所以不能用于 try-with-resources;而 catch 只捕获特定异常,无法覆盖 RuntimeException、Error 等未声明异常,只有 finally 能 100% 保证执行。
- 即使
sharedValue++触发NullPointerException或OutOfMemoryError,finally仍会运行 - 若在
catch里写unlock(),而异常发生在lock()之前(比如构造参数校验失败),就会误释放未持有的锁 - 别信“我这逻辑很稳,不会出异常”——JVM 层面的
StackOverflowError或 GC 暂停都可能打断流程
嵌套 lock() 时,unlock() 次数必须严格匹配
可重入不等于“自动配平”。每次 lock() 都会使内部计数器 +1,每次 unlock() -1;计数器归零才真正释放锁。少一次 unlock(),锁就一直被占着。
- 递归调用或 AOP 切面里不小心重复加锁,容易导致计数失衡
- 不要靠“感觉”数调用次数,用 IDE 的结构高亮或静态检查工具辅助识别嵌套层级
- 如果真需要多次加锁,建议用局部变量记录当前持有次数,或改用
tryLock()+ 重试逻辑来规避深度嵌套
示例(合法但需谨慎):
lock.lock();
try {
lock.lock(); // 第二次加锁
try {
// ...
} finally {
lock.unlock(); // 对应第二次
}
} finally {
lock.unlock(); // 对应第一次
}
公平锁与非公平锁对 try-finally 没有影响,但会影响等待行为
无论构造时传 true 还是 false,try-finally 结构和释放逻辑完全一样。区别只在锁竞争时的调度策略。
- 非公平锁(默认):允许刚唤醒或新来的线程“插队”,吞吐更高,但可能让等待久的线程饿死
- 公平锁(
new ReentrantLock(true)):按lock()调用顺序排队,避免饥饿,但上下文切换开销略大 - 公平性开关不改变锁的生命周期管理逻辑,别因为它而省略
finally,也别因为它是非公平的就放松对重入计数的检查
真正容易被忽略的是:公平锁下,tryLock() 的超时行为依然遵循 FIFO,但非公平锁下它可能立刻成功——这点在压测时才会暴露。

















