JUC之可重入锁

92 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情

可重入锁

可重入锁又叫递归锁,指的是同一线程外层函数获得锁之后,内存递归函数仍然能获取该锁的代码,同一个线程在外层获取锁的时候,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步者的代码块。

ReentrantLock/synchronized就是一个典型的可重入锁

代码验证

synchronized

image-20200926153106088

public class ReenterLockDemo {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendSMS();
        }, "t1").start();

        new Thread(() -> {
            phone.sendSMS();
        }, "t2").start();
    }
}

class Phone {
    public synchronized void sendSMS(){
        System.out.println(Thread.currentThread().getName()+"\t invoked sendSMS()");
        sendEmail();
    }

    public synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getName()+"\t invoked sendEmail()");
    }
}

image-20200926153317986

当t1线程执行sendSMS()方法后,该方法中调用来同步方法sendEmail()。线程t1拿到外层函数sendSMS的锁后,能继续执行内层函数sendEmail。这就是可重入锁。

ReentrantLock

image-20200926153723845

public class ReenterLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendSMS();
        }, "t1").start();

        new Thread(() -> {
            phone.sendSMS();
        }, "t2").start();
    }
}

class Phone {
    Lock lock = new ReentrantLock();

    public void sendSMS() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
            sendEmail();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void sendEmail() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

image-20200926153317986

ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样。

image-20200926153851074

sendEmail()方法中加锁了两次,而只解锁了一次。就会导致t1线程执行完sendEmail后死锁,t2线程只能不停等待t1结束!

image-20200926154035177

这也验证了ReentrantLock/synchronized就是一个典型的可重入锁。