可重入锁

180 阅读2分钟

一、概念

可重入锁,又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提:锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

可重入锁的一个优点是可一定程度避免死锁。

1. 可重入锁的种类

  1. 隐式锁(即synchronized关键字使用的锁),默认是可重入锁。由JVM控制加锁和释放锁
  2. 显示锁(即Lock),也有ReentrantLock这样的可重入锁。需要手动控制加锁和释放锁

二、synchronized锁

1. synchronized锁的使用

synchronized锁有两种:同步代码块、同步方法。

  • 同步代码块
public class ReenterLockDemo {

    public void m1() {
        Object lockA = new Object();
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "---外层调用");
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "---中层调用");
                    synchronized (lockA) {
                        System.out.println(Thread.currentThread().getName() + "---内层调用");
                    }
                }
            }
        }, "t1").start();
    }

    public static void main(String[] args) {
        new ReenterLockDemo().m1();
    }
}
  • 同步方法
public class ReenterLockDemo {

    public synchronized void m1() {
        System.out.println("外层循环");
        m2();
    }

    private synchronized void m2() {
        System.out.println("中层循环");
        m3();
    }

    private synchronized void m3() {
        System.out.println("内层循环");
    }

    public static void main(String[] args) {
        new ReenterLockDemo().m1();
    }
}

2. synchronized锁的原理

1692783504412.jpg

1692783619712.jpg

如上图所示,使用javap -c命令编译后,可以看到有个monitorenter和monitorexit命令,这就是自动加锁和释放锁。但是还有一个单独的moniterexit,它是为了保证发生异常情况时仍然能释放锁。

  • 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
  • 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,JVM会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
  • 在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么JVM可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
  • 当执行monitorexit时,JVM需要将锁对象的计数器减1。计数器为0代表锁已释放。