bigywater.tech
Java 内部原理 · 91

synchronized 可重入性真实验证:同一线程递归4层拿同一把锁真实不会死锁

"Java的锁是可重入的"是常识,但很少有人认真想过它到底可重入到什么程度。这篇真实用递归和父子类方法调用链验证边界,再用真实的跨线程对照,确认"可重入"到底是谁的特权。

同一线程4层递归真实不死锁
真实验证:main线程递归调用4层都进入同一个synchronized(lock),真实全部成功,没有阻塞自己
重入只对持锁线程本身有效
真实验证:main线程持锁期间,另起的线程真实必须等待锁释放才能进入,重入不能跨线程

上一篇:泛型边界真实验证

真实验证:同一线程递归重入同一把锁,不会死锁

用递归方法反复进入synchronized(lock)代码块,真实验证同一线程能否顺利多层嵌套。

SynchronizedReentrancyDemo.java static void recurse(int n) { synchronized (lock) { depth++; if (n > 0) recurse(n - 1); // 同一线程再次进入同一把锁 depth--; } } recurse(3); // 递归4层
真实实测
真实进入第 1 层 synchronized(lock),线程: main
真实进入第 2 层 synchronized(lock),线程: main
真实进入第 3 层 synchronized(lock),线程: main
真实进入第 4 层 synchronized(lock),线程: main
单线程递归重入4层锁,真实未死锁,耗时: 8ms

真实结果证明了可重入锁的核心机制:JVM 为每把锁真实维护一个"持有者线程"和一个"重入计数器"。当 main 线程第一次进入 synchronized(lock) 时,计数器真实变成 1;递归调用再次尝试获取同一把锁时,JVM 检测到请求方就是当前持有者,真实直接把计数器加 1 放行,不会阻塞;每次退出代码块,计数器真实减 1,直到归零锁才被真正释放。4 层递归全部真实顺利完成,没有出现死锁。

真实验证:子类重写方法调用 super.父类方法(),同样可重入

另一种常见的重入场景:子类的synchronized方法里调用super.同名synchronized方法,两者锁的是同一个this对象。

SynchronizedReentrancyDemo.java class Child extends Parent { @Override synchronized void parentMethod() { super.parentMethod(); // 调用父类版本,同一个this,同一把锁 } }
真实实测
真实进入 Child.parentMethod()(重写版本)
真实进入 Parent.parentMethod()

真实结果证明了这个真实存在但容易被忽视的场景:Child.parentMethod() 和 Parent.parentMethod() 都是实例方法级别的 synchronized,锁的对象真实都是同一个 this(调用者实例)。调用链从子类版本进入父类版本,本质上就是同一线程对同一把锁的又一次获取,真实顺利完成,不会自己把自己锁死。

真实验证:重入是"线程"的特权,不是"代码"的特权

容易被误解的一点:重入不是因为"代码里嵌套调用了synchronized",而是因为"同一个线程"在请求。用真实的跨线程对照来验证这条边界。

SynchronizedReentrancyDemo.java synchronized (lock) { // main线程持锁 Thread other = new Thread(() -> { synchronized (lock) { secondThreadGotLock.set(true); } // 换了线程 }); other.start(); Thread.sleep(200); }
真实实测
main线程持有锁200ms内,其他线程真实能否进入: false(应为false,重入只对持锁线程自己有效)
main线程释放锁后,其他线程真实能否进入: true
真实的边界在哪。这组对照证明了重入机制的关键限定:JVM 判断"能否重入"的依据是"发起请求的线程是否就是当前锁的持有者",而不是"这段代码是否已经在synchronized块内部"。main 线程持有 lock 期间,另起的线程真实完全按照普通的锁竞争规则等待——200ms 内它真实没能进入,直到 main 线程退出代码块、真正释放锁之后,才真实成功进入。这也是为什么"可重入锁"能解决的是"同一线程自己调用自己"的死锁风险,完全不改变多线程之间正常的互斥竞争关系。

分步演示:同一个线程反复进入同一把 synchronized 锁,真实发生了什么

每一行都是真实运行这段代码得到的结果。

synchronized 的可重入靠 JVM 内部的重入计数器实现;持有锁这件事真实是按线程隔离记录的,不是全局状态
这一步新验证的重入场景

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,递归重入和跨线程对照重复运行结果完全一致。
    • 和ReentrantLock那篇一起看,能理解为什么"可重入"这个名字会被显式地放进 ReentrantLock 的类名里——它和 synchronized 关键字提供的是同一层级的重入语义保证。
    • 没有涉及:重入计数器在字节码/对象头层面(Mark Word)的真实存储位置、不可重入锁(如某些自定义的原始CAS锁)在同一线程重复获取时真实死锁的对照实验——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号