ReentrantLock的实现原理

        在java这条不归路上,随着我们经历的项目越来越多,花里胡哨的东西也会见了不少,总会有些许小膨胀。抽颗烟静下心来想一想(我不抽),也只是会用而已,要是想彻底掌握,还得静下心来去学习。征服自己不算什么本事,你得去让面试官闭嘴(开玩笑~)。

        在多个线程去访问公共资源的时候,我们知道很容易引发数据错乱和数据安全等问题。为了避免这种问题,java提供了synchronized和lock这俩种锁。我们先看看这俩种锁到底有什么不同吧!

        synchronized是java底层支持的,而lock所在的concurrent包则是jdk实现的。synchronized是一个关键字,lock是一个接口(我们这里主要用到的是ReentrantLock这个实现类)。再去看一下它们分别是怎么去释放锁的:

        synchronized释放锁的情况有俩种:

        1、获取到锁的线程执行完代码块主动去释放锁

        2、线程执行发生异常,此时JVM会让线程自动释放锁。

        lock释放锁就只有一种情况了,就是调用unlock()方法(这也是和synchronized区别比较大的一点,synchronized不会主动去释放锁,而lock必须主动的去调用unlock()方法,否则会造成死锁现象)。因此如果这个获取锁的线程由于要等待IO或者其他原因被阻塞了,但是又不会主动去释放锁,其他线程便只能等待,效率方面就会受到较大的影响。

         我们先来看看lock这个接口都有哪些实现:

ReentrantLock的实现原理_第1张图片
lock的实现

        接下来我们去看一看lock这个接口里边到底有些什么东西:

ReentrantLock的实现原理_第2张图片
lock接口中的方法

        下面就是对接口中的方法进行介绍:

        lock():这个就是获取锁的方法了,若锁被其他线程获取,则等待(阻塞)。 

        lockInterruptibly():可中断地获取锁,该方法会响应中断,在锁的获取过程中可以中断当前线程。这个就算是lock的一个招牌了,因为在使用synchronized时,一个线程在等待获取锁的过程中是不能中断的;而在使用lockInterruptibly()方法去获取锁的时候,如果获取不到,是可以在锁的获取过程中响应中断的。

        tryLock():它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

        tryLock(long time, TimeUnit unit):方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

        unlock():这个就是释放锁了。

        newCondition():这个太明显了,主要是去获取一个Condition实例。因为Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知。

        上边就是对lock这个接口一些大概的介绍了,今天我们主要去看看ReentrantLock。

        ReentrantLock是一种重入锁,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁,该线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞。该锁还提供了获取锁时的公平和非公平性选择。

        先去看一下ReentrantLock的构造方法:

ReentrantLock的实现原理_第3张图片
构造方法

        我们就以非公平锁为例,进去看一看:

ReentrantLock的实现原理_第4张图片
lock方法的具体实现

        没拿到锁怎么办啊,我们看看它是怎么处理的:

acqiire()

        这个方法是AbstractQueuedSynchronizer这个类里边的,先大概的知道一下它都干了什么:

        tryAcquire():会尝试再次通过CAS获取一次锁。

        addWaiter():从表面意思来看就是让他去排队去了(这个我们后边会再做解释)

        acquireQueued():进入等待状态,等待其他线程释放锁之后唤醒自己,直到获取到锁之后返回。

        selfInterrupt():如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt()。

        到了这里,有必要去看一下AbstractQueuedSynchronizer这个东西了,先用我们的散装英语猜一下就知道个大概了,这是个抽象的同步队列,我们去看一下这个队列到底是个什么东西:

ReentrantLock的实现原理_第5张图片
继承结构

        说一下这里的state,其实就是标识是否拿到锁了,state为0的时候没拿到,state为1表示拿到锁了。

ReentrantLock的实现原理_第6张图片
AbstractQueuedSynchronizer的重要属性

        有头有尾的,从命名可能就会有一些想法了,再去看看这个Node:

ReentrantLock的实现原理_第7张图片
Node

        好了,真相大白了,双向链表都被玩出花来了。

        我们回过头来,再去看看没有拿到锁acqiire()会执行的那几个操作:

        1、首先是再次去尝试获取锁 --- tryAcquire:

ReentrantLock的实现原理_第8张图片
nonfairTryAcquire

       2、然后是将节点放入双向链表中 --- addWaiter():

ReentrantLock的实现原理_第9张图片
addWaiter()

        这里提一下CAS自旋是个什么鬼?

        说一下上边的enq()方法。进入循环的第一次,拿到链表的尾节点,然后去判断,尾节点是不是null,是null的话就说明这个链表就根本不存在,于是创建一个新的链表,让头结点指向尾节点;然后进入第二次循环,t肯定不为null了,就进入else了,将当前节点的前置节点指向尾节点,然后再进行CAS操作,如果t真的和内存中的尾节点是一样的,那么node就作为尾节点放入链表中,然后返回;如果CAS失败了的话(被其他线程改了),就再循环,直到CAS成功。

       3、 等待被唤醒 --- acquireQueued():

ReentrantLock的实现原理_第10张图片
acquireQueued()

        锁上好了,去看看怎么开锁!

unlock()

        和获取锁一样,还是一个一个的点进去看:

ReentrantLock的实现原理_第11张图片
release()

        再去看看它具体怎么释放锁的:

ReentrantLock的实现原理_第12张图片
tryRelease()

        获取锁和解锁就这些东西了,解锁相对来说没那么复杂。画个流程图展示一下上锁的整个过程吧!

ReentrantLock的实现原理_第13张图片
获取锁的流程

        经过这些分析之后,我们知道了lock是否获取到锁是通过修改AQS(AbstractQueuedSynchronizer)中的state属性来实现的,如果是1就是拿到锁了,是0就是没拿到,>1就是lock作为一个重入锁的体现了,没有获取到锁的线程会放到同步队列的尾部,它其实是一个双向链表,然后通过自旋操作,不断的去唤醒线程,再去通过CAS获取锁,大概就是这个过程了。

        好多都是在这里学的,文章本身写的就很清晰,我这里边学边总结,做个记录。大家有什么不明白的可以来这看看一文带你理解Java中Lock的实现原理-云栖社区-阿里云。

        多学习,多总结,多记录。是学习也是蜕变,不驰于空想,不骛于虚声 !

你可能感兴趣的:(ReentrantLock的实现原理)