摘要
synchronized
关键字具备 可重入性 (Reentrancy),同一线程在持有锁的情况下,可以再次获取同一把锁而不会阻塞。本文将从概念、代码示例、JVM 实现机制和工程实践四个方面,深入解析 synchronized
的可重入性。
正文
一、什么是可重入性?
可重入性(Reentrant)是指 同一线程在持有锁时,可以重复进入被同一个锁保护的临界区,而不会发生死锁。
换句话说:如果锁是"可重入"的,那么一个线程在进入某个 synchronized
方法/代码块后,还能继续调用另一个需要同一把锁的 synchronized
方法。
二、代码示例
1. 方法递归调用
java
public class ReentrantDemo {
public synchronized void methodA() {
System.out.println(Thread.currentThread().getName() + " 进入 methodA");
methodB(); // 再次请求同一个锁
}
public synchronized void methodB() {
System.out.println(Thread.currentThread().getName() + " 进入 methodB");
}
public static void main(String[] args) {
ReentrantDemo demo = new ReentrantDemo();
new Thread(demo::methodA, "T1").start();
}
}
结果:线程 T1
先进入 methodA
,随后 不会阻塞 ,而是直接进入 methodB
,因为这是同一线程在请求同一把对象锁。
2. 父子类继承中的可重入性
java
class Parent {
public synchronized void doSomething() {
System.out.println("父类方法执行");
}
}
class Child extends Parent {
@Override
public synchronized void doSomething() {
System.out.println("子类方法执行");
super.doSomething(); // 依然是同一把锁
}
}
结果:子类方法调用父类方法不会造成死锁,因为锁是可重入的。
三、JVM 如何实现可重入性?
在 JVM 中,synchronized
是基于 对象头中的 Monitor(监视器锁) 实现的。
Monitor 内部维护了一个 计数器(recursions) ,用于记录线程重入的次数。
- 第一次获取锁:计数器从 0 → 1,线程成为锁的拥有者。
- 同一线程再次获取锁:计数器 +1。
- 退出同步块/方法:计数器 -1。
- 计数器归零:锁才真正释放,其他线程才能获取。
这就是为什么 synchronized
能避免 同一线程自我阻塞 的根本原因。
四、可重入性的优势
- 避免死锁
- 如果没有可重入性,线程在调用
methodA()
的时候进入methodB()
就会死锁。
- 提升编程灵活性
- 允许在继承、递归调用中安全地使用
synchronized
。
- 降低锁使用的复杂度
- 开发者无需手动判断当前线程是否持有锁,简化了并发编程模型。
五、工程实践建议
- 理解可重入,但不要滥用
- 可重入不是性能优化手段,而是一种安全保证。
- 不要因为"可重入"就随意嵌套锁,仍需控制临界区大小。
- 注意递归调用的深度
- 虽然可重入避免了死锁,但无限递归依然可能导致栈溢出。
- 对比 ReentrantLock
ReentrantLock
也支持可重入,并提供了更多功能(可中断、公平锁、条件队列)。- 在需要灵活控制时,可以考虑替代
synchronized
。
六、总结
synchronized
的可重入性是并发编程中的重要特性:
- 同一线程可以重复进入锁保护的代码,不会造成自我死锁;
- 底层通过 Monitor 的计数器 实现;
- 在递归、继承等复杂场景下尤为重要。
理解可重入性,可以帮助我们在设计并发程序时写出更加 安全、简洁、易维护 的代码。