AQS源码解析

今天清明节放假正好趁此总结一下AQS的源码分析😊😊😊

AbstractQueuedSynchronizer 的简称,Doug Lea大师编写的AQS源码,其实就是对CAS的封装和丰富,用于实现同步器(如锁和信号量等)的基础框架

其实就是一个通用的同步框架,设置这样一个框架要满足以下几点:

  • 通用性:在实现底层必要的同步机制后,给上层调用留有空间,也就是保持对外接口的简单性和纯粹性
  • 利用CAS,原子的修改共享标志位
  • 等待队列:让队列的头部进行自旋获取标志位,而其他的线程则挂起

AQS的一个成员变量state,就是来判断共享资源是否被占用的标志位,设置为int类型是为了记录重入的次数,还有就是共享模式下表示线程的数量

然后它还有一个表示队列头尾的head、tail指针,这两个指针维护了一个FIFO的双向链表来对线程进行管理

java 复制代码
 1 private transient volatile Node head;
 2 private transient volatile Node tail;

队列中的每一个节点Node其实就是对线程的封装,其中也包括四种线程在队列中的等待状态,然后Node中方法也很简单,其中有一个就是获取前置Node

队列中的节点有两种模式,共享和独占,我说一下独占把,ReentrantLock底层就是基于独占模式的AQS来实现的

我们设计这样一个框架其实有两种主要的使用场景:

  • 尝试获得锁,不管有没有获得立即返回
  • 必须获得锁,如果当前锁被占用,则进行等待

源码中的tyrAcquire和acquire正好对应了这两种场景:

tyrAcquire在AQS中是直接抛出异常,是为了让使用的上层自己实现,因为在尝试获得锁的过程中可能包含业务自定义的逻辑,比如是否可重入

acquire方法是重要的,acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

addWaiter(Node.EXCLUSIVE),arg方法是将当前线程封装为一个Node,然后加入等待队列,因为是符合FIFO的,所以通过CAS操作将Node添加到队尾

这里它采用了快速插入的机制,就是一开始先假设并发量比较少(就是前置节点不为空)的情况下CAS进行插入,如果插入失败,则执行enq(node);方法来循环尝试插入

在队列中插入成功后返回一个Node,接下来就是acquireQueued方法

它首先去拿当前节点的前置节点,如果前置节点为head,说明是队首然后当前节点有权限来尝试获得锁,如果不是队首,然后看当前线程是否需要挂起等待,如果需要就挂起等待,如果不需要则继续循环判断是否有权限来尝试获得锁

当前线程是否需要挂起等待的逻辑:

它判断它的前置节点是否也在等待获得锁,如果是,则当前这个线程就挂起等待,如果当前线程是取消状态,则从队列中取消该节点

如果判断当前节点需要被挂起,则执行下面方法:

就是将当前线程挂起阻塞在这,一直等到持有锁的线程来调用relese方法来唤醒

大量线程挂起,然后得有relese释放,来看一下relese的释放:

和tryAcquire一样,它的tryRelese也是交给上层去实现

主要是relese方法,来看unparkSuccessor方法是怎么释放的:

它是从队尾依次向前搜索,找到head后面最靠前的并且是非CANCELED的节点来唤醒,调用unpark

从后往前搜索是为了避免在多线程下新建Node节点时没有与后面节点建立Next连接导致搜索中断

相关推荐
Y***h1871 小时前
第二章 Spring中的Bean
java·后端·spring
稚辉君.MCA_P8_Java2 小时前
DeepSeek 插入排序
linux·后端·算法·架构·排序算法
t***p9352 小时前
idea创建SpringBoot自动创建Lombok无效果(解决)
spring boot·后端·intellij-idea
d***81722 小时前
解决SpringBoot项目启动错误:找不到或无法加载主类
java·spring boot·后端
无限大62 小时前
RBAC模型:像电影院选座一样管理权限,告别"一个用户配一个权限"的噩梦
后端
间彧2 小时前
在CI/CD流水线中如何集成自动化的发布验证和熔断机制?
后端
间彧2 小时前
如何处理蓝绿部署中的数据迁移和数据库版本兼容性问题?
后端
间彧2 小时前
什么是金丝雀/灰度发布
后端
间彧3 小时前
什么是蓝绿部署
后端
爷_3 小时前
Golang: sqlc 和 goose 最佳实践
后端·go·全栈