可重入锁的核心是同一线程可多次获取同一把锁而不阻塞,通过线程专属计数器实现,解决单线程自我阻塞问题;但它不能防止多线程循环等待导致的死锁,需配合锁顺序、超时等设计规范。

Java 可重入锁的核心,在代码中体现为“同一线程可多次获取同一把锁而不阻塞”,它本身不是用来避免所有死锁的万能方案,而是专门解决单线程因重复加锁导致的自我阻塞这一类死锁。真正避免死锁,靠的是设计规范(如锁顺序、超时、tryLock)和可重入机制的配合。
可重入性在代码中的直观体现
可重入性最直接的体现,是同一个线程在嵌套调用或递归中,能顺利进入多个被同一把锁保护的代码块。关键在于:锁内部记录了“谁持有”和“持有几次”。
-
synchronized 的隐式可重入:无需额外操作,JVM 自动维护计数器。
例如:一个同步方法调用另一个同步方法,两者锁的是同一个对象实例,主线程不会卡在第二次进入上。 -
ReentrantLock 的显式可重入:必须手动 lock() 和 unlock(),且调用次数必须严格匹配。
例如:outerMethod() 中 lock() 后调用 innerMethod(),innerMethod() 再次 lock() 不会阻塞;但 finaly 中只 unlock() 一次,会导致计数器不归零,锁未真正释放。
可重入锁如何防止“单线程死锁”
这类死锁不是多线程互相等待,而是线程自己把自己卡住。没有可重入性时,下面的逻辑会永远停住:
- 线程 A 进入 methodA(),获取锁 L;
- methodA() 内部调用 methodB(),methodB() 也要获取锁 L;
- 若锁不可重入,线程 A 在已持 L 的情况下请求 L,就会无限等待自己释放——形成死锁。
可重入锁通过线程专属计数器化解:第一次 lock() 计数=1,第二次 lock() 计数=2;第一次 unlock() 计数=1,只有第二次 unlock() 计数=0,锁才真正释放。这保证了递归、回调、父子类同步方法调用等场景的正确性。
立即学习“Java免费学习笔记(深入)”;
注意:可重入锁 ≠ 全面防死锁
ReentrantLock 或 synchronized 都无法防止典型的多线程循环等待死锁。比如两个线程分别先抢锁 A 再抢锁 B,但顺序相反,照样死锁。
- 不能靠“可重入”来绕过锁顺序不一致的问题;
- 避免多线程死锁,要靠:统一加锁顺序、使用 tryLock(timeout) 设置超时、减少锁粒度、用 LockSupport 或 Condition 替代盲目 wait/notify;
- ReentrantLock 提供的 isHeldByCurrentThread()、getHoldCount() 等方法,可用于调试锁状态,但不直接防死锁。
写法要点与常见错误
用好可重入锁,关键是守规矩:
- ReentrantLock 必须配对使用:每次 lock() 都要有对应的一次 unlock(),且都在 finally 块中执行;
- 不要在持有锁时调用外部不可控代码(如回调、用户传入的函数),以防对方再申请锁引发意外循环;
- synchronized 虽自动管理,但要注意锁对象是否一致——不同对象、this 和 Class 锁混用,会导致可重入失效;
- 子类重写父类 synchronized 方法并调用 super.xxx(),能正常重入,前提是锁目标相同(如都是 this)。


















