"Java的锁是可重入的"是常识,但很少有人认真想过它到底可重入到什么程度。这篇真实用递归和父子类方法调用链验证边界,再用真实的跨线程对照,确认"可重入"到底是谁的特权。
上一篇:泛型边界真实验证
用递归方法反复进入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 层递归全部真实顺利完成,没有出现死锁。
另一种常见的重入场景:子类的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
每一行都是真实运行这段代码得到的结果。