一文读懂AQS的前世今生

一、AQS是什么?

AQS本质就是JUC包下的一个抽象类AbstractQueuedSynchronizer。AQS本身不提供并发下的一个具体的功能,都是JUC包的一些工具类,锁之类的内容,需要基于AQS去实现。

更多的AQS是作为一个基础类,让其他的类去继承。

AQS中大致有三个核心内容

  • state属性: 本质就是一个int类型,但是这个属性及其重要。比如ReentrantLock,如果state为0,代表没有线程持有当前锁资源。如果state >0代表某个线程在持有当前锁资源。
private volatile int state;
  • 同步队列(双向链表):一个由Node对象组成的同步队列。如果某个线程获取锁资源失败了,当前线程需要排队等一会,这个同步队列就是排队等待的地儿。
  • 单向链表: 也是由Node对象组成的一个单向链表。比如持有锁的线程,执行了await方法,当前线程会封装为Node,添加到这个单向链表中。

单向链表

二、AQS核心底层和Lock是什么关系?

ReentrantLock意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。为了帮助大家更好地理解ReentrantLock的特性,我们先将ReentrantLock跟常用的Synchronized进行比较,其特性如下:

ReentrantLock就是借助了AQS实现的锁机制。只不过ReentrantLock类没有直接去的继承AQS,而是他的内部类Sync去继承的AQS,实现的一些具体的业务逻辑。

锁的一些逻辑是由抽象类Sync实现的,一些特有的功能,比如和公平以及非公平锁先关的内容是Sync的子类

  • NonfairSync: 非公平锁
  • FairSync: 公平锁

使用ReentrantLock可以指定是公平锁不是非公平锁,可以通过ReentrantLock提供的有参构造来决定使用哪种方式,默认情况下,使用的是非公平锁。如果参数传递为true,采用公平锁。

优先分析一下lock方法的逻辑。

发现ReentrantLock执行lock方法后,会直接调用sync的lock方法。但是sync没有直接实现lock方法的逻辑,是由NonfairSync和FairSync去实现的具体逻辑。

先看NonfairSync:

加锁总结:

  • 通过ReentrantLock的加锁方法Lock进行加锁操作。
  • 会调用到内部类Sync的Lock方法,由于Sync#lock是抽象方法,根据ReentrantLock初始化选择的公平锁和非公平锁,执行相关内部类的Lock方法,本质上都会执行AQS的Acquire方法。
  • AQS的Acquire方法会执行tryAcquire方法,但是由于tryAcquire需要自定义同步器实现,因此执行了ReentrantLock中的tryAcquire方法,由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法,因此会根据锁类型不同,执行不同的tryAcquire。
  • tryAcquire是获取锁逻辑,获取失败后,会执行框架AQS的后续逻辑,跟ReentrantLock自定义同步器无关。

解锁总结:

  • 通过ReentrantLock的解锁方法Unlock进行解锁。
  • Unlock会调用内部类Sync的Release方法,该方法继承于AQS。
  • Release中会调用tryRelease方法,tryRelease需要自定义同步器实现,tryRelease只在ReentrantLock中的Sync实现,因此可以看出,释放锁的过程,并不区分是否为公平锁。
  • 释放成功后,所有处理由AQS框架完成,与自定义同步器无关。

三、AQS如何尝试获取资源?

发现tryAcquire在AQS中并没有具体的实现逻辑,需要子类自己去实现在ReentrantLock中,就提供了两种实现的机制:

非公平锁源码实现

公平锁源码实现

四、AQS获取资源失败如何排队?

当线程基于tryAcquire获取锁资源失败后,需要执行addWaiter方法,去AQS的同步队列中排队。这里的逻辑确保AQS同步队列初始化有监控节点后,将当前节点插入到最后面~~~不存在失败的情况,死循环插不成功就一直尝试。源码如下:

如果扔到同步队列失败,基于eng保证可以去排队AQS的同步队列,在头部的节点是一个伪/监控的节点在AQS的同步队列还没有初始化时,先初始化一个Node,作为监控节点然后再将当前获取锁资源失败的线程排到最后面。

主要的流程总结如下:

  • 通过当前的线程和锁模式新建一个节点。
  • Pred指针指向尾节点Tail。
  • 将New中Node的Prev指针指向Pred。
  • 通过compareAndSetTail方法,完成尾节点的设置。这个方法主要是对tailOffset和Expect进行比较,如果tailOffset的Node和Expect的Node地址是相同的,那么设置Tail的值为Update的值。

五、AQS排队后如何重新尝试获取资源?

前面获取锁资源失败了,并且已经基于addWaiter扔到同步队列排队之后,要走当前逻辑acquireQueued方法如果Node节点是排在head.next位置的,直接尝试抢锁如果没抢到,或者是不是排在head.next位置的。

  • 确保自己能够被唤醒
  • 挂起线程 (当释放锁后,如果head节点的状态是-1,就会唤醒后续节点)

最后

赠人玫瑰,手留余香~ LZ整理了一整套2023年最新的面试资料,如果有需要的小伙伴点击此处即可~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值