AQS源码深入解析
2021/10/26 14:40:30
本文主要是介绍AQS源码深入解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
AQS理解
AQS是什么
AQS(AbstractQueuedSynchronizer 抽象的队列同步器)
抽象的指的时AQS是ReentrantLock,CountDownLauch,SemaPhore,CyclicBarrier等类的基础框架是,定义了这些类实现的模板是一个抽象类,使用了模板设计模式。队列同步器指的是其内部使用的是一个双向队列,用于管理那些抢占资源失败的线程。
AQS的内部结构
从源码看AQS中维护着一个volatile声明的变量state用于标识当前资源类的使用状态,还包含两个指针,这两个指针分别指向AQS锁维护队列的头指针和尾指针。
而这个Node则指的是AQS内部声明的一个内部类,它将所有需要进入等待队列的thread分装成一个node用于管理这些线程的转态。
这个内部类的内部最重要的就是上面四个成员变量,waitstatus用于标识当前等待的状态用于后续抢占资源使用,thread则是那个被封装的thread,pre和tail指的是它所维护的队列中的前后指针。
大致流程
我们先进行大致流程的解析,再一步步看源码。
首先我们在上一步确定了AQS的结构,其实AQS的运行就是这个结构的创建和销毁过程。
我们举一个例子来说明流程:银行排队办理业务现在有A,B,C三个用户来办理业务。银行的座椅上的顾客代表着当前占用资源的线程,同时我们在银行上方有一个标记如果为1就有人正在办理,如果为0就窗口空闲。
①A顾客先办理业务,要办理十分钟,它先判断标志位如果为0,则将标记设置为1,然后坐下。这就是我们第一个线程设置state和设置线程的相应代码。
②接着顾客B来办理,判断标志位为1,它试着抢占座位看第一位顾客是否刚刚好办理结束,如果不是,则创建一个队列增加一个哨兵来节点相当于在它前面增加一个空位置,然后再次试着抢占,失败,最后设置乖乖落座
③A办理结束通知B线程来抢占
④B线程结束中断,查看state为0,执行如同①的步骤
从ReenrantLock解析AQS源码
ReentrantLock和AQS的关系
ReentrantLock内部有一个sync内部类继承自AQS它使用这个内部类来调用AQS中锁定义的接口
Reentrantlock的lock方法底层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZSthTDs-1635229134990)(4.png)]
lock是一个抽象方法它的底层就是调用sync内部类中的lock方法,而这个方法又是一个抽象方法,它的底层实现有公平锁和非公平锁两个。这两者的区别在于公平锁是根据等待时间调度,非公平锁则是根据先来先得的原则调度的。ReentranctLock默认实现的是非公平锁,我们可以根据ReentranctLock的构造方法传入一个true让其实现公平锁,但是单从性能上来说非公平锁的较好。
①我们往非公平锁方向看其内部的底层实现是这样的
意思是先通过CAS原子替换判断comparAndSetState的值是否成功,这个方法的底层调用的是unsafe类的CAS方法,而这里的state指的是我们上面所介绍的AQS中的state(用于标识当前资源类是否再使用中0表示空闲,1表示当前有线程正在调度),如果进入这个if那么就将设置一个排他锁将当前当前资源类的线程设置位当前线程。
②如果当前资源类正在被调度就会进入到下一个分支中的acquire方法
if中判断两个方法的作用分别如下,我们逐个看
a、tryAcquire方法用于判断试着抢占锁是否成功
这里就用到了模板模式,将能够抽象的方法抽象的足够高让底层的类来实现,本来应该定义成一个抽象方法,但是我们这将其定义为一个抛出异常的方法,让底层的类必须重写也能达到一样的效果。
接下来我们查看其中非公平的实现方式
它先获取到state的值如果空闲就直接上位,将state设置位1,将执行的线程换成自己。(这描述着一种情况A线程刚刚调用结束,B线程就来刚刚好抢占到锁,还有一种情况就是A线程调用结束唤醒B线程来调用。)
第二个if判断的是这样一种情况就是A线程刚刚调用结束A又来抢占,这样系统就会把资源优先分配给A,让它接着使用。
否则返回false
接着我们调用下一个方法
b、acquireQueued(addWaiter(Node.EXCLUSIVE))
我们首先调用的是addwaiter方法
先查看我们的队列中是否有元素,如果队列尾部为空则创建以恶搞空的node作为哨兵节点作为占位符和抢占的标志位,然后再调用enq方法将本节点插入到队列的尾部,插入的过程是先将需要插入节点的前指针设置为哨兵节点,然后设置尾节点位当前节点最后将哨兵节点的尾节点指向当前节点(需要注意的是只有第一次插入时需要创建哨兵节点,后续插入只要将节点插入到队尾即可)
接着我们回到acquiredQueued方法
在插入到队列中之后先获取前节点来再次尝试抢占资源如果抢占,我们先假设当前有节点在占用资源往下看进入到shouldParkAfterFailedAcquire方法
由于节点哨兵节点的waitstatus(节点的等待状态位)为0则进入到else,这个方法中使用一个原子替换将哨兵节点的值waitStatus设置为-1返回false,由于上述程序一直自旋所以会再次进入到这个判断中这次ws为-1直接return true。
往下进入ParkAndCheckInterupt方法走将其设置为中断,至此排队线程真正中断在队列中,等待正在使用资源的线程释放锁将其唤醒
接着我们看unlock类中的调度过程
unlock()与lock()一样,调用sync中的release()方法我们同样看非公平锁的实现
先调用调用tryRelease方法
同样的模板模式我们选择ReentranctLock
这个方法中主要做了两件事,设置state为0,设置资源当前的调用线程为null
执行完这个方法之后回到unRelease方法中将头结点的中断解除
重新回到lock
解除之后由于刚刚被上锁的线程还是自旋状态,它会接着运行,它的运行轨迹是。
回到这个自旋接着执行tryAcquire,这时state为0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvN7DZFa-1635229135000)(10.png)]
抢占成功,修改state为1,资源的执行线程为当前线程。
接着我们还有一些后续的操作,删除队列张的线程,同一个线程不可能在两个地方出现。我们的解决方法是首先切除断当前节点与少冰节点的联系,让它被gc,然后当头结点设置为当前节点,然后将当前节点,接着当前节点的Thread为空让当前节点作为哨兵。
抢占成功,修改state为1,资源的执行线程为当前线程。
接着我们还有一些后续的操作,删除队列张的线程,同一个线程不可能在两个地方出现。我们的解决方法是首先切除断当前节点与少冰节点的联系,让它被gc,然后当头结点设置为当前节点,然后将当前节点,接着当前节点的Thread为空让当前节点作为哨兵。
这篇关于AQS源码深入解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-02Java管理系统项目实战入门教程
- 2024-11-02Java监控系统项目实战教程
- 2024-11-02Java就业项目项目实战:从入门到初级工程师的必备技能
- 2024-11-02Java全端项目实战入门教程
- 2024-11-02Java全栈项目实战:从入门到初级应用
- 2024-11-02Java日志系统项目实战:初学者完全指南
- 2024-11-02Java微服务系统项目实战入门教程
- 2024-11-02Java微服务项目实战:新手入门指南
- 2024-11-02Java项目实战:新手入门教程
- 2024-11-02Java小程序项目实战:从入门到简单应用