一步一步解析AQS源码(非公平锁为例)

最近在学习周阳老师的AQS,写一些笔记,如果觉得哪里看不懂的可以去看一下周阳老师的JUC课。

在开始我们要先说明一下AQS的第一基础知识。

AQS底层其实是一个双向链表。
AQS有五种等待状态

 

首先以ReentrantLock的非公平锁的lock方法为切入点。

 发现lock调了一个叫做sync的lock方法,step into可以看到这个sync是Sync的一个实例,而这个

Sync类继承了AbstractQueuedSynchronizer,也就是我们的AQS。

 继续step into,选择我们的非公平锁子类。

 这个时候就出现了三个方法,首先看第一个方法compareAndSetState(),它将我们的State修改为1,表示已经有线程开始处理业务,当线程结束的时候会将state设置为0。

如果获取锁成功,就执行setExclusiveOwnerThread()表示将独占线程设置为当前线程。

如果获取锁失败就执行acquire(1)方法。假设我们现在设置state失败,执行acquire方法

 acquire方法中也包含了四个方法下面来一一介绍这三个方法

 tryAcquire:

先找到实现子类

 一直step into找到nonfairTryAcquire方法。我们先看两个判断条件,第一个条件是"c==0",这个c也就是通过getState获得的State。大家是否还记得我们在最开始的lock()中设置过这个state表示有线程在执行业务。那么第一个if的逻辑也就十分好理解了,就是再来判断一次,独占线程是否已经执行结束,如果state=0就表示没有线程执行当前业务,就将自己设置为独占线程。假设此时State=1,我们去第二个判断,发现当前线程也不是独占线程。于是直接返回false

 回到我们的acquire,tryAcquire为false取反为true,执行第二个方法acquireQueued,我们先看这个addWaiter方法是个什么东西。

我们看到了next,prev肯定会联想到双向链表,也印证了我们开始说的AQS底层是一个双向链表。我们先将当前线程,以及EXCLUSIVE模式封装进一个Node对象,用来表示当前线程。此时的tail还为空(注意我们是第二个线程,第一个线程在执行第一个lock方法的时候就直接返回了并没有执行到这一步)因为我们的tail还为空,所以进入enq方法

传入的参数是我们上一步封装的Node对象()。这里又一次获取了我们的tail,但是此时还是空,尝试将当前的Head初始化,新建一个node来当head(为了防止和上面得到Node对象混淆我们叫他newNode),并且将tail也指向这个newNode,然后进行下一次循环,此时tail已经不为空执行else的逻辑,else的逻辑就是将这个newNode和我们上一步封装的node形成双向链表,并且将上一步得到的node设置为tail。

 当前的节点图示如下

 获取到了节点,返回到我们的acquire,我们进入acquireQueued方法

这个node.predecessor()就是用来获取当前线程节点的前一个节点,也就是我们的NewNode。然后看第一个if,不难看出当前线程不死心,又去看看在执行业务的线程退出没有,退出了的话就让自己抢占业务,并且将自己设置为头节点。我们这里假设还有其他线程抢占业务。

 先总结,这个方法目前来说对我们的作用就是将上一个节点的等待状态设置为Single状态。我们先来看shouldParkAfterFailedAcquire。他的逻辑也不难。 首先就是去获取上一个节点的等待状态。我们的上一个节点也就是我们的newNode并没有设置他的waitStatue,默认为0.就去执行我们的esle逻辑,将我们的newNode的等待状态设置为single,然后跳回上一步,开始第二次循环,再次进入shouldParkAfterFailedAcquire,因为此时newNode的等待状态已经是single,所以返回true

然后返回上一步,执行我们的parkAndCheckInterrupt方法。这里就把使用了park将我们的当前线程锁住了,当前线程暂停。当这里我们的lock也就完成了。

接下来我们看unlock逻辑,前面的就不多赘述,一直到我们的release方法

 然后先看tryRelease方法

 因为只有占有了线程才能unlock,所以这个getState得到的一定是1(上面我们也提到了),传入的参数是1,所以c=0.然后进入if逻辑。将当前的独占线程设置为空,将状态位设置为0.返回true。

 再来看我们的release,头节点的等待状态我们在上面设置过为Single。所以进入判断执行unparkSuccessor

 所以调用unparkSuccessor方法,传入的是这个newNode也就是head。因为waitStatus=-1<0,所以调用compareAndSetWaitStatus,将等待状态修改为0,然后获取这个new Node的下一个节点,也就是我们上面park的节点节点,因为这个park节点的waitstatus为0,并且不为空,所以就调用unpark唤醒b线程。

最后我们来看park的线程被唤醒,返回到acquireQueued,开始第二次循环,此时他就能抢占到业务了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值