1. 前置知识
- 公平锁和非公平锁
- 可重入锁
- 自旋锁
- LockSupport
- 数据结构之双向链表
- 设计模式之模板设计模式
AQS重要性
JAVA ------>JVM
AQS ------>AQS
2. AQS入门级别理论知识
2.1 是什么?
2.1.1 字面意思
Abstract Queued Synchronizer----抽象的队列同步器
源码位置:
AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer是AbstractOwnableSynchronizer的子类
并且是一个抽象类:
2.1.2 技术解释
- 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题
- 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量
表示持有锁的状态
CLH队列(FIFO)
CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
2.2 AQS为什么是JUC内容中最重要的基石?
2.2.1 和AQS关联的技术
JUC的以下技术都是以AQS为基石
- Semaphore
- CycleBarrier
- ReentranReadWriteLock
- CountDownLatch
- ReentranLock
AQS子类:
2.2.2 锁和同步器的关系
- 锁,面向锁的使用者
- 定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
- 同步器,面向锁的实现者
- 比如Java并发大神DougLee,提出统一规范并简化了锁的实现,
屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
- 比如Java并发大神DougLee,提出统一规范并简化了锁的实现,
2.3 AQS能干嘛?
因为:加锁会导致阻塞
所以:有阻塞就需要排队,实现排队必然需要队列
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node
),通过CAS
、自旋
以及LockSupport.park()
的方式,维护state变量
的状态,使并发达到同步的效果。
3 AQS内部体系架构
3.1 AQS自身
3.1.1 AQS的int变量state-volatile修饰
代表同步状态标志位
- 0代表资源没有被线程占用,资源现在是空闲状态
- 1代表资源被线程占用,资源现在不是空闲状态
3.2 内部类Node(Node类在AQS类内部)
3.2.1 Node对象两种模式
- SHARED(共享模式)
- 标志节点正在等待共享模式的标记
- EXCLUSIVE(独占模式)
- 标志节点正在独占模式等待的标记
3.2.2 Node的Node的int变量waitState-volatile修饰
每个Node节点在AQS队列中的等待状态,Node初始化时waitState为0
- CANCELLED(取消)=1
- 表示该Node节点为取消状态,需要出队
- SIGNAL(标志)=-1
- 表示该节点的后一个节点需要unparking(LockSupport里的知识)
- CONDITION(条件)=-2
- 表示该节点正在等待某种条件激活
- PROPAGATE(传播)=-3
- 指示下一个 acquireShared 应该无条件传播的 waitStatus 值
只有CANCELLED状态是大于0的,判断时有需要先记住
3.2.3 prev&next 双向链表
记录该节点的前一个节点和后一个节点
3.3 以上得出结论:AQS同步队列的基本结构
CLH:Craig、Landin and Hagersten 队列,是个单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO)
4 AQS源码分析之ReentranLock
4.1 架构
上图可知:
- ReentranLock实现了Lock接口
- ReentranLock中的内部类NonfairSync(非公平锁)和FairSync(公平锁)继承了Sync
- Sync又继承了AQS抽象类
4.2 ReentranLock公平锁与非公平锁
上图可以得知:
- new ReentranLock()和new ReentranLock(false)都是非公平锁
- new ReentranLock(true) 表示公平锁
4.2.1 公平锁和非公平锁
4.2.1.1 公平锁和非公平锁的lock方法
提前了解acquire方法会调用tryAcquire,不管公平锁还是非公平锁最后都会调用acquire方法
- 因为非公平的的tryAcquire不讲武德,不排队所以我们在抢锁之前需要看看锁的状态status是否为0,不然队列中所有节点不管status是否为0都去强太耗费性能.
- 而公平锁在tryAcquire的时候将武德,只有成为头结点才会去抢,所以直接acquire
4.2.1.2 非公平锁的tryAcquire
非公平锁tryAcquire返回值:
- 当AQS status为0返回true
- 是重入锁的情况下返回true
nonfairTryAcquire()方法
4.2.1.3 公平锁的tryAcquire
公平锁tryAcquire返回值:
- 当AQS status为0并且当前节点是头结点时返回true
- 是重入锁的情况下返回true
参考下图及结论:
!hasQueuedPredecessors()
的意思就是如果在当前线程之前有排队的线程就不尝试去抢锁了,老老实实的排队,这不就是公平锁吗?非公平锁没有这个条件约束,所以不管是排在那个位置的线程都会去抢一抢
hasQueuedPredecessors()方法
- 如果在当前线程之前有排队的线程,则为true ;
- 如果当前线程位于队列的头部或队列为空,则为false
总结:
- 可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:
hasQueuedPredecessors():
hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法
4.3 以非公平锁为例继续后面的源码分析
tryAcquire方法在4.2章节以及分析后续我们主要针对
- addWaiter
- acquireQueued
- selfInterrupt
以上三个方法进行分析
4.3.0 acquire方法总流程
4.3.1 addWaiter(Node.EXCLUSIVE)
将加锁失败的节点加入队列
当tryAcquire()返回false的情况下才会执行addWaiter
公平锁:
以下条件只要包含一个就返回false
- status不等于0
- 当前节点不是头结点
- 也不是可重入锁
非公平锁
以下条件只要包含一个就返回false
- status不等于0
- 也不是可重入锁
enq(Node node)
4.3.2 acquireQueued(final Node node, int arg)
- 将队列中各节点的等待状态waitStatus进行管理
- waitStatus为-1的节点park
- waitStatus为1的移出队列;
- 其他状态值会逐步修改为-1,
- 最后将waitStatus为-1的节点对应的线程park
- 在addWaiter的大自旋中会不断tryAcquire(),检测共享资源是否解锁
addWaiter后会返回最新的尾节点
predecessor()方法 返回尾结点的前置节点
shouldParkAfterFailedAcquire(Node pred, Node node)
该方法主要作用就是将修改抢锁失败的节点waitStatus为-1等待状态,或者将想要取消的节点从队列中移除
如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起
parkAndCheckInterrupt()
阻塞当前节点
4.3.3 selfInterrupt()
抢锁失败,加入队列成功那么就中断当前线程
4.4 unlock解锁源码分析
解锁操作,主要作用是将头结点后为等待状态-1的节点unpark,然后唤醒阻塞的线程,以及一些异常处理
unlock主要就是调用AQS的释放方法release
release开始会tryAcquire尝试解锁
tryAcquire尝试解锁
unparkSuccessor方法将中断节点恢复至正常状态