通过ReentrantLock源码看AQS源码实现

ReentrantLock是基于AQS实现的,通过ReentrantLock的公平锁研究一下AQS线程竞争做出的操作
其中AQS比较重要的几个属性先介绍一下:
**
private transient volatile Node head; 队列头
private transient volatile Node tail; 队列尾
private volatile int state; 锁状态**
Node node 这个节点包含了三个属性
volatile Node prev; 上一个节点
volatile Node next; 下一个节点
volatile Thread thread; 线程

可以先把这个node理解为一个链表的数据结构
假如现在是t1线程进来
首先先调用公平锁的lock方法,加锁

在这里插入图片描述
执行AQS封装的方法,
在这里插入图片描述
然后查看ReentrantLock公平锁自己的实现
先执行第一个tryAcquire方法,这个方法会去反,先记一下
先拿到当前线程,当前线程为t1,获取状态,状态默认为0,没有被修改过,然后==0,进去执行hasQueuedPredecessprs方法 这里也是去反

在这里插入图片描述
将队列尾tail属性给到t,头head属性给到h,然后定义一个s,这里队列还没有被初始化,只是做了一个赋值操作 然后判断h!=t 这里这两个属性都是默认为null的属性,所以null!=null返回false,然后短路跳出返回false
在这里插入图片描述
去反得到true,开始执行compareAndSetState方法,cas设置状态,调用lock时传入的值为1,期望值为0,修改state即为抢占锁
在这里插入图片描述
下面这个方法就是把当前线程给到一个属性,然后执行完毕
在这里插入图片描述
返回true,证明拿到锁
在这里插入图片描述
如果是第二次再进来加锁,就会走下边的方法,因为他第一次进来已经吧状态改为了1,下面就是看ReentrantLock的可重入锁判断,判断当前线程是不是持有锁的线程,如果是就状态+1,表示重入了锁一次

然后假如t1开始执行任务,然后t2进来开始竞争锁
t2也调用lock方法

在这里插入图片描述

上面这个tryAcquire方法t2走了以后就会返回false,然后去反,开始执行acquireQueued(addWaiter(Node.EXCLUSIVE),arg),先看addWaiter方法
首先是创建一个当前线程的node节点,然后创建一个新的node节点然后把尾属性tail给它,现在pred是一个null,因为tail是null,然后判断null!=null,返回false,执行enq方法
在这里插入图片描述
一个死循环,创建一个新的node节点,tail还是null,所以t也是null,然后cas设置头,给head创建了一个新的node,然后把head头给到tail,现在这这里队列算是初始化了,头和尾都指向同一个node,但这个node属性是没有值的,然后再次循环,这次tail不等于null,然后让这个node节点指向t这个节点,然后cas设置t的尾,尾就是当前线程的node,现在队列里空的node节点在第一个,next指向t2的node,然后t2的prev执行第一个节点,把第一个node节点返回

在这里插入图片描述
执行完这个将这个当前线程的node返回出去
在这里插入图片描述
然后开始执行这个方法,进来以后定义两个属性开始死循环,然后取出node的perv,这里是当前线程的node,所以他的perv是第一个node,然后第一个node是==head的,然后自旋一次获取锁,如果获取所成功,就把头设置为当前线程的node,然后把第一个的next节点指向null,这样没有了饮用第一个node就可以被gc回收,然后获取锁成功,这里我们假设没有成功,执行下边的shouldParkAfterFailedAcquire
在这里插入图片描述
第一个参数node是队列第一个node,第二个node是当前线程的node,然后拿出前一个node的ws状态,
ws为0,signal为-1,判断小于0,不小于,然后cas设置这个状态为-1,这个-1会表示前面那个线程进入了睡眠,后面会在解锁时候用到,然后返回false,循环以后会再一次自旋去拿锁,如果再次拿锁失败,那么再进入当前方法,这次就等于-1了,返回true,然后会执行parkAndCheckInterrupt方法
在这里插入图片描述

在这里插入图片描述
就会执行这个方法,调用park方法,让线程进入睡眠
这里解释一下为什么前面要调用两三次自旋去拿锁,因为如果调用了park方法,那么就会让os参与,也就是说,变成了一把重量级锁,所以会想让他自旋拿锁,如果拿到锁,不调用park方法,那么他就是一个轻量级锁,效率肯定是会高于重量级锁的,然后睡眠以后就看解锁
在这里插入图片描述
在这里插入图片描述
先调用tryRelease方法
在这里插入图片描述
这里拿到锁的状态,锁为1表示加1次锁,为2表示重入了一次.一次类推,这里是t1只进入了一次,就是1,1-1所以c=0,然后设置当前锁线程为null,然后设置锁状态为0,返回free为true,然后把第一个node给h,这里是判断是否有线程在队列,然后在判断状态是否为0,如果不为0表示有线程在等待,就执行unpark方法,唤醒在等待的第一个线程继续执行,完成t1的解锁

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_48358308/article/details/110086837
今日推荐