什么是可重入锁和不可重入锁?

1 个回答

  • 夜太
    明知不可为而为之就是我最大的诚意
    ### 1、可重入锁和不可重入的区别 可重入锁也叫递归锁,是在一个线程获取锁后,内部如果还需要获取锁,可以直接获取的锁(前提锁对象得是同一个对象或者class)。 不可重入锁也就是相反,线程获取锁后,内部不能再获取锁,由于之前已经获取过还没释放而阻塞,会导致线程死锁。 所以可重入锁的一个优点是可一定程度避免死锁。 可重入锁有ReentrantLock和synchronized。 非可重入锁有NonReentrantLock。 ### 2、代码示例: 我们根据代码来看看: ```java public class Demo2 { public synchronized void doSomething() { System.out.println("方法1执行..."); doOthers(); } public synchronized void doOthers() { System.out.println("方法2执行..."); } } ``` 在这个类中两个方法都被synchronized所修饰,在doSomething()中调用doOthers()方法。因为synchronized锁是可重入的,所以在一个线程中调用doOthers()时就可以直接获得当前对象的锁,进入doOthers()。 如果是一个不可重入锁,在当前线程在doSomething()中调用doOthers时,因为doOthers()需要获取当前锁对象,需要都Something()释放掉当前锁,而实际上当前锁已经被当前线程所持有,自然是没办法释放,就造成死锁了。 那可重入锁是怎样在嵌套调用时就能获得这个锁,从而解决这个问题呢?这里我们通过我找到的一个小例子和源码来探究到底是怎样实现的。 ### 3、图析 一个打水的例子,假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。 现在有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁。 ![image.png](https://images.codewd.com/6ff0d1e1df65396a274be789c05c419e.png) 非可重入锁的话,此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。当前线程出现死锁,整个等待队列中的所有线程都无法被唤醒。 ![image.png](https://images.codewd.com/6a9bb8634ca79cbf80820705b57b863a.png) ### 4、源码对比 之前提到重入锁ReentrantLock以及非可重入锁NonReentrantLock,那我们可以通过ReentrantLock和NonReentrantLock的源码来对比分析一下实现原理。 首先ReentrantLock和NonReentrantLock都继承父类AQS(AbstractQueuedSynchronizer),其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。 当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。 释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。 ![image.png](https://images.codewd.com/e9a8b8a5a196fa770003b10e7bec8af0.png)
    发布于 2022-02-10 13:31 •IP属地 西藏
    举报
    评论
    收藏