10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)
前言
上篇文章15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized有说到synchronized由object monitor实现的
object monitor中由cxq栈和entry list来实现阻塞队列,wait set实现等待队列,从而实现synchronized的等待/通知模式
而JDK中的JUC并发包也通过类似的阻塞队列和等待队列实现等待/通知模式
这篇文章就来讲讲JUC的基石AQS(AbstractQueuedSynchronizer)
需要了解的前置知识:CAS、volatile
如果不了解CAS可以看上篇讲述synchronized的文章(链接在上面)
如果不了解volatile可以看这篇文章 5个案例和流程图让你从0到1搞懂volatile关键字
本篇文章以AQS为中心,深入浅出描述AQS中的数据结构、设计以及获取、释放同步状态的源码流程、Condition等
观看本文大约需要10分钟,可以带着几个问题去观看
- 什么是AQS,它是干啥用的?
- AQS是使用什么数据结构实现的?
- AQS获取/释放同步状态是如何实现的?
- AQS除了具有synchronized的功能还拥有什么其他特性?
- AQS如何去实现非公平锁、公平锁?
- 什么是Condition?它跟AQS是什么关系?
AQS数据结构
什么是AQS呢?
AQS是一个同步队列(阻塞队列),是并发包中的基础,很多并发包中的同步组件底层都使用AQS来实现,比如:ReentrantLock、读写锁、信号量等等...
AQS有三个重要的字段,分别是: head 头节点、tail 尾节点、state 同步状态
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
//同步状态
private volatile int state;
}
头尾节点很好理解,因为AQS本身就是个双向链表,那么state同步状态是什么?
AQS中使用同步状态表示资源,然后使用CAS来获取/释放资源,比如设置资源为1,一个线程来尝试获取资源,由于同步状态目前为1,于是该线程CAS替换同步状态为0,成功后表示获取到资源,之后其他线程再来获取资源就无法获取了(状态为0),直到获取资源的线程来释放资源
上述获取/释放资源也可以理解成获取/释放锁
同时三个字段都被volatile修饰,用volatile来保证内存可见性,防止其他线程修改这些数据时当前线程无法感知
通过上面的描述,我们可以知道AQS大概长这样
当某个线程获取资源失败时,会被构建成节点加入AQS中
节点Node是AQS中的内部类,Node中有些重要的字段一起来看看
static final class Node {
//节点状态
volatile int waitStatus;
//前驱节点
volatile Node prev;
?
//后继节点
volatile Node next;
//当前节点所代表的线程
volatile Thread thread;
?
//等待队列使用时的后继节点指针
Node nextWaiter;
}
prev、next、thread应该都好理解
AQS同步队列和等待队列都使用这种节点,当等待队列节点被唤醒出队时,方便加入同步队列
nextWaiter就是用于节点在等待队列中指向下一个节点
waitStatus表示节点的状态
状态 | 说明 |
---|---|
INITIAL | 0 初始状态 |
CANCELLED | 1 该节点对应的线程取消调度 |
SIGNAL | -1 该节点对应的线程阻塞,等待唤醒竞争资源 |
CONDITION | -2 该节点在等待(条件)队列中,等待唤醒后从等待队列出队进入同步队列竞争 |
PROPAGATE | -3 共享情况下,会唤醒后续所有共享节点 |
不太理解状态不要紧,我们后文遇到再说
经过上面的描述,节点大概是长成这样的
AQS中还有另外一个内部类ConditionObject
用于实现等待队列/条件队列,我们后文再来说说
AQS中可以分为独占、共享模式,其中这两种模式下还可以支持响应中断、纳秒级别超时
独占模式可以理解为同一时间只有一个线程能够获取同步状态
共享模式可以理解为可以有多个线程能够获取同步状态,方法中常用shared
标识
方法中常用acquire
标识获取同步状态,release
标识释放同步状态
这些方法都是模板方法,规定流程,将具体的实现留给实现类去做(比如获取同步状态,该如何获取交给实现类去实现)
独占式
独占式实际就是时刻上只允许一个线程独占该资源,多线程竞争情况下也只能有一个线程获取同步状态成功
获取同步状态
不响应中断的独占获取和响应中断、超时的类似,我们以acquire
为例查看源码
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
方法用于尝试获取同步状态,参数arg表示获取多少同步状态,获取成功返回true 则会退出方法,留给实现类去实现
addWaiter
addWaiter(Node.EXCLUSIVE) 构建独占式节点,并用CAS+失败重试的方式加入AQS的末尾
private Node addWaiter(Node mode) {
//构建节点
Node node = new Node(Thread.currentThread(), mode);
//尾节点不为空则CAS替换尾节点
Node pred =