可重入锁的核心是“同一线程可重复获取同一把锁”,Java 中 synchronized 和 ReentrantLock 都是可重入锁,写法各有特点。下面分别两种锁的可重入用法示例,清晰展示“重复获取锁”的场景:
一、synchronized 可重入写法(隐式可重入,JVM 自动管理)
synchronized 天生支持可重入,同一线程在持有锁时,调用同一锁对象的其他同步方法/代码块,会自动重复获取锁(无需手动操作)。
示例:同步方法的可重入
public class SynchronizedReentrantDemo {// 锁对象(当前实例 this)public synchronized void methodA() {System.out.println("进入 methodA(已获取锁)");// 同一线程调用同一锁的 methodB(可重入)methodB();System.out.println("退出 methodA(释放部分锁计数)");}// 同一锁对象(this)的同步方法public synchronized void methodB() {System.out.println("进入 methodB(重复获取锁,锁计数+1)");// 还可以继续调用其他同步方法(如 methodC),依然可重入System.out.println("退出 methodB(锁计数-1)");}public static void main(String[] args) {SynchronizedReentrantDemo demo = new SynchronizedReentrantDemo();// 启动线程调用 methodAnew Thread(demo::methodA).start();}
}
执行结果:
进入 methodA(已获取锁)
进入 methodB(重复获取锁,锁计数+1)
退出 methodB(锁计数-1)
退出 methodA(释放部分锁计数)
原理:
JVM 会为 synchronized 锁维护一个“重入计数器”:
- 线程首次获取锁时,计数器=1;
- 同一线程再次获取同一锁时,计数器+1;
- 线程退出同步块/方法时,计数器-1;
- 当计数器=0 时,锁才真正释放,其他线程可获取。
示例:同步代码块的可重入
public class SynchronizedBlockReentrantDemo {private final Object lock = new Object(); // 自定义锁对象public void methodX() {synchronized (lock) { // 首次获取 lock 锁(计数器=1)System.out.println("进入 methodX(已获取锁)");methodY(); // 同一线程调用同一锁的 methodYSystem.out.println("退出 methodX(计数器-1)");}}public void methodY() {synchronized (lock) { // 重复获取 lock 锁(计数器=2)System.out.println("进入 methodY(锁计数器+1)");System.out.println("退出 methodY(计数器-1)");}}public static void main(String[] args) {new Thread(new SynchronizedBlockReentrantDemo()::methodX).start();}
}
执行结果:
进入 methodX(已获取锁)
进入 methodY(锁计数器+1)
退出 methodY(计数器-1)
退出 methodX(计数器-1)
二、ReentrantLock 可重入写法(显式可重入,手动控制)
ReentrantLock 是显式可重入锁,需手动调用 lock() 获取锁、unlock() 释放锁,重入时同样会增加计数器,释放时需对应减少计数器(lock() 和 unlock() 次数必须相等)。
示例:
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo {private final ReentrantLock lock = new ReentrantLock(); // 可重入锁public void method1() {lock.lock(); // 首次获取锁(计数器=1)try {System.out.println("进入 method1(已获取锁)");method2(); // 同一线程调用 method2,重复获取锁} finally {lock.unlock(); // 释放一次(计数器=0,锁真正释放)System.out.println("退出 method1(释放锁)");}}public void method2() {lock.lock(); // 重复获取锁(计数器=2)try {System.out.println("进入 method2(锁计数器+1)");} finally {lock.unlock(); // 释放一次(计数器=1)System.out.println("退出 method2(释放部分锁)");}}public static void main(String[] args) {new Thread(new ReentrantLockDemo()::method1).start();}
}
执行结果:
进入 method1(已获取锁)
进入 method2(锁计数器+1)
退出 method2(释放部分锁)
退出 method1(释放锁)
关键注意:
ReentrantLock 的 lock() 和 unlock() 必须成对出现,重入几次就要释放几次,否则会导致锁无法真正释放(比如重入2次但只释放1次,计数器=1,其他线程永远无法获取锁)。
三、可重入锁的核心价值
避免“线程自我阻塞”:如果锁不可重入,同一线程在 methodA 中获取锁后,调用 methodB 时会因再次请求同一锁而阻塞(自己等自己),导致死锁。可重入锁通过计数器机制解决了这一问题,确保同一线程能流畅执行嵌套的同步逻辑(如事务嵌套、递归加锁等场景)。
总结
synchronized:隐式可重入,无需手动管理计数器,适合简单场景。ReentrantLock:显式可重入,需手动控制lock()/unlock()次数,适合需要公平锁、超时等高级特性的场景。
两种锁的可重入性都依赖“计数器”机制,核心是“同一线程重复获取锁时不阻塞,释放对应次数后才真正释放锁”。