ReentrantLock锁与AQS的联系

请先看——————>AQS的简单说明

        ReentrantLock底层是基于AQS实现的,在并发编程中可以实现公平锁非公平锁来对同步资源进行控制,默认是非公平锁,并且是可重入锁。  

1.ReentrantLock的体系关联结构

ReentrantLock锁与AQS的联系_第1张图片

2.构造方法

ReentrantLock锁与AQS的联系_第2张图片 可以发现ReentrantLock的非公平锁和公平锁实际是NonfairSync()和FairSync()这两个类来实现的。

3.非公平锁和公平锁的实际区别

ReentrantLock锁与AQS的联系_第3张图片

因为sync中的lock()方法是抽象方法,所以NonfairSync和FairSync类里面会重写lock()方法。  

ReentrantLock锁与AQS的联系_第4张图片 ReentrantLock锁与AQS的联系_第5张图片

        发现在调用lock方法的唯一区别就是,非公平锁会先尝试CAS去修改状态来抢占锁,如果修改成功,则抢占锁成功并返回,如果没有修改成功,则调用acquire(1)方法。而公平锁直接是调用acquire(1)方法。  

4.非公平锁的实现

4.1tryAcquire()方法

ReentrantLock锁与AQS的联系_第6张图片

进入到acquire方法后,先调用tryAcquire(1)方法............  

ReentrantLock锁与AQS的联系_第7张图片         发现此方法内还是要进行尝试抢占锁,如果此时正好state状态为0了,那么CAS进行尝试修改状态,如果成功直接返回。还会判断正在占用锁的线程和当前线程是不是同一个,如果是,则证明是要重入锁,也返回true,如果这两个都没有成功,则返回false。

4.2公平锁和非公平锁在tryAcquire方法中的区别

ReentrantLock锁与AQS的联系_第8张图片

        公平锁的tryAcquire()方法中,就算此刻状态变为0了,不会直接CAS,而是会先判断FIFO队列中是否有等待的线程,如果有,那么就不进行CAS操作了,因为要保证公平!  

4.3addwaiter()方法

ReentrantLock锁与AQS的联系_第9张图片

需要注意的是,在双向链表中,第一个结点为虚结点(也叫做哨兵结点),只是占位用的,所以真正有用的数据结点是从第二个结点开始的。

进入到addwaiter()方法,当双向链表中尾指针为null时,需要进行初始化,创建虚结点并将封装线程的Node结点入队。当尾指针不为null时,将封装线程的Node结点入队,并将尾指针指向Node结点。

执行完毕后返回加入的Node结点。

4.4acquireQueued()方法

ReentrantLock锁与AQS的联系_第10张图片

 ReentrantLock锁与AQS的联系_第11张图片

        我们将Node入队之后,执行acquireQueued方法,先拿到Node的前驱结点,判断是否为虚拟头结点,如果是,那么直接尝试tryAcquire()去获取锁,如果获取到了,将虚拟头结点指向Node,原来的虚拟头结点指向null,等待GC回收即可,但是如果Node的前驱结点不是虚拟头结点或没获取到锁,那么修改其前驱结点的状态直到为SIGNAL,前驱结点的状态修改完后,然后利用LockSuppor.park()将当前Node进行阻塞。

        那现在我们的好奇心飙升了,加入的Node将它的前驱结点状态修改了,然后自己又阻塞了,那么Node什么时候唤醒unPark呢?一个线程占用锁后,调用了unlock()方法解锁,那么unlock()方法有什么猫腻呢?

4.5unlock()方法

ReentrantLock锁与AQS的联系_第12张图片

ReentrantLock锁与AQS的联系_第13张图片 

4.6unlock后发生的一些事情

        一个线程释放锁后,会唤醒虚拟头结点后的第一个结点,唤醒之后,还在acquireQueued()方法中,然后进行for循环,因为此时锁此时已经释放了,此时唤醒的Node的前驱就是虚拟头结点,调用tryAcquire()方法去获取锁,获取到后将虚拟头结点指向Node,原来的虚拟头结点指向null,等待GC回收即可。队列中一直后面的线程Node就还处于阻塞的状态。

你可能感兴趣的:(并发编程,java,开发语言,并发编程)