可重入锁与不可重入锁之间的区别与性能差异

参考答案

1. 可重入锁

指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。

为了避免死锁的发生,JDK 中基本都是可重入锁。

测试下 synchronized 和  java.util.concurrent.lock.ReentrantLock 锁的可重入性:

 synchronized 加锁,可重入性

public class TestSynchronizedReentrant {
    
    public static void main(String[] args) {
        new Thread(new SynchronizedReentrant()).start();
    }
    
}

class SynchronizedReentrant implements Runnable {

    private final Object obj = new Object();
    
    /**
     * 方法1,调用方法2
     */
    public void method1() {
        synchronized (obj) {
            System.out.println(Thread.currentThread().getName() + " method1()");
            method2();
        }
    }
    
    /**
     * 方法2,打印前获取 obj 锁
     * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
     */
    public void method2() {
        synchronized (obj) {
            System.out.println(Thread.currentThread().getName() + " method2()");
        }
    }

    @Override
    public void run() {
        //线程启动 执行方法1
        method1();
    }
    
}

结果:

Thread-0 method1()
Thread-0 method2()

2.  测试 ReentrantLock 的可重入性

public class TestLockReentrant {

    public static void main(String[] args) {
        new Thread(new LockReentrant()).start();
    }
    
}

class LockReentrant implements Runnable {

    private final Lock lock = new ReentrantLock();
    
    /**
     * 方法1,调用方法2
     */
    public void method1() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " method1()");
            method2();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 方法2,打印前获取 obj 锁
     * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
     */
    public void method2() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " method2()");
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        //线程启动 执行方法1
        method1();
    }
    
}

结果:

Thread-0 method1()
Thread-0 method2()

3. 测试不可重入锁

由于在 JDK 中没找到不可重入锁,所以自己尝试实现。

两种方式:通过 synchronized wait notify 实现;通过 CAS + 自旋方式实现。

3.1  synchronized wait notify方式实现

public class NonReentrantLockByWait {

    //是否被锁
    private volatile boolean locked = false;
    
    //加锁
    public synchronized void lock() {
        //当某个线程获取锁成功,其他线程进入等待状态
        while (locked) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //加锁成功,locked 设置为 true
        locked = true;
    }
    
    //释放锁
    public synchronized void unlock() {
        locked = false;
        notify();
    }

}

 

3.2  通过 CAS + 自旋方式实现

import java.util.concurrent.atomic.AtomicReference;

public class NonReentrantLockByCAS {
    
    private AtomicReference<Thread> lockedThread = new AtomicReference<Thread>();

    public void lock() {
        Thread t = Thread.currentThread();
        //当 lockedThread 持有引用变量为 null 时,设置 lockedThread 持有引用为 当前线程变量
        while (!lockedThread.compareAndSet(null, t)) {
            //自旋,空循环,等到锁被释放
        }
    }
    
    public void unlock() {
        //如果是本线程锁定的,可以成功释放锁
        lockedThread.compareAndSet(Thread.currentThread(), null);
    }
}

测试类:

public class TestLockNonReentrant{

    public static void main(String[] args) {
        new Thread(new LockNonReentrant()).start();
    }

}


class LockNonReentrant implements Runnable {
    
//    private final NonReentrantLockByWait lock = new NonReentrantLockByWait();
    private final NonReentrantLockByCAS lock = new NonReentrantLockByCAS();
    
    /**
     * 方法1,调用方法2
     */
    public void method1() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " method1()");
            method2();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 方法2,打印前获取 obj 锁
     * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
     */
    public void method2() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " method2()");
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        //线程启动 执行方法1
        method1();
    }
}

测试结果,都是在 method1,调用 method2 的时候,导致了死锁,线程一直等待或者自旋下去。

以上,是Java面试题【可重入锁与不可重入锁之间的区别与性能差异】的参考答案。

 

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧