[java 锁 - 03 重入写法 ]

可重入锁的核心是“同一线程可重复获取同一把锁”,Java 中 synchronizedReentrantLock 都是可重入锁,写法各有特点。下面分别两种锁的可重入用法示例,清晰展示“重复获取锁”的场景:

一、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();
        // 启动线程调用 methodA
        new 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(); // 同一线程调用同一锁的 methodY
            System.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(释放锁)

关键注意
ReentrantLocklock()unlock() 必须成对出现,重入几次就要释放几次,否则会导致锁无法真正释放(比如重入2次但只释放1次,计数器=1,其他线程永远无法获取锁)。

三、可重入锁的核心价值

避免“线程自我阻塞”:如果锁不可重入,同一线程在 methodA 中获取锁后,调用 methodB 时会因再次请求同一锁而阻塞(自己等自己),导致死锁。可重入锁通过计数器机制解决了这一问题,确保同一线程能流畅执行嵌套的同步逻辑(如事务嵌套、递归加锁等场景)。

总结

  • synchronized:隐式可重入,无需手动管理计数器,适合简单场景。
  • ReentrantLock:显式可重入,需手动控制 lock()/unlock() 次数,适合需要公平锁、超时等高级特性的场景。
    两种锁的可重入性都依赖“计数器”机制,核心是“同一线程重复获取锁时不阻塞,释放对应次数后才真正释放锁”。
posted @ 2025-10-26 17:10  十三山入秋  阅读(5)  评论(1)    收藏  举报