可重入锁与非可重入锁
可重入锁与非可重入锁
可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。
Java中的ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点就是可以避免死锁,下面结合源码分析
public class Wedget{
public synchronized void doSomething(){
System.out.println("方法1执行");
doOthers();
}
public synchronized void doOthers(){
System.out.println("方法2执行");
}
}
在上面的代码中,类中的两个方法都是被synchronized修饰的,doSomething方法中调用doOthers方法。因为内置锁是可重入的,所以同一个线程在调用doOthers时可以直接获得对象锁,进入doOthers进行操作。
如果是非可重入锁,在调用doOthers时需要先释放doSomething时获得当前对象的锁释放掉,实际上该锁已经被当前线程持有,而且无法释放,将会造成死锁。
为什么可重入锁就可以在嵌套调用时自动获得锁呢?
可重入锁图示:
有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水 桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够 打到水。这就是可重入锁。
非可重入锁图示:
但如果是非可重入锁的话,此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。当前线程出现死锁,整个等待队列中的所有线程都无法被唤醒。
之前我们说过ReentrantLock和synchronized都是重入锁,那么我们通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁。
首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来 计数重入次数,status初始值为0。 当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行 同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个 锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更 新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。 释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重 入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。
还没有评论,来说两句吧...