AQS

AQS简介

说明

AQS,AbstractQueudSynchronizer,即队列同步器。


可一并参考ReentrantLock,其内部底层是:定义一个内部类Sync继承了AQS),公平锁FairSync和非公平锁NonfairSync又继承了Sync

AQS架构

  • 上图中有颜色的为Method,无颜色的为Attribution。
  • 总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。
  • 当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。

原理

AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。 AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

AQS内部变量都被volatile修饰了

1
2
3
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

同时变量的操作都会经过CAS来保证操作安全。(CAS原理查看CAS.md篇)

1
2
3
4
//其中一个方法
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

同时内部节点node内部变量也被volatile修饰了

1
2
3
4
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
内置的FIFO队列就是CLH(Craig, Landin, and Hagersten) 同步队列

该队列是一个FIFO双向队列

入队,调用的是addWaiter(Node node),尝试快速(相当于执行了一次)将该节点设置尾成尾节点,设置失败内部调用enq(final Node node)方法。enq(final Node node)通过“自旋”也就是死循环的方式来保证该节点能顺利的加入到队列尾部,只有加入成功才会退出循环,否则会一直循序直到成功。上述两方法都是通过CAS保证节点的添加的原子性。

出队,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。

AQS数据结构:

如果当前线程获取同步状态失败(锁)时,当前线程以及等待状态等信息构造成一个节点(Node)

解释一下几个方法和属性值的含义:

方法和属性值 含义
waitStatus 当前节点在队列中的状态
thread 表示处于该节点的线程
prev 前驱指针
predecessor 返回前驱节点,没有的话抛出npe
nextWaiter 指向下一个处于CONDITION状态的节点(由于本篇文章不讲述Condition Queue队列,这个指针不多介绍)
next 后继指针

线程两种锁的模式:

模式 含义
SHARED 表示线程以共享的模式等待锁
EXCLUSIVE 表示线程正在以独占的方式等待锁

waitStatus有下面几个枚举值:

枚举 含义
0 当一个Node被初始化的时候的默认值
CANCELLED 为1,表示线程获取锁的请求已经取消了
CONDITION 为-2,表示节点在等待队列中,节点线程等待唤醒
PROPAGATE 为-3,当前线程处在SHARED情况下,该字段才会使用
SIGNAL 为-1,表示线程已经准备好了,就等资源释放了
同步状态

通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程)。

独占锁

共享锁

AQS的应用场景:

同步工具 同步工具与AQS的关联
ReentrantLock 使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。
Semaphore 使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数。
CountDownLatch 使用AQS同步状态来表示计数。计数为0时,所有的Acquire操作(CountDownLatch的await方法)才可以通过。
ReentrantReadWriteLock 使用AQS同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数。
ThreadPoolExecutor Worker利用AQS同步状态实现对独占线程变量的设置(tryAcquire和tryRelease)。

AQS
http://www.muzili.ren/2022/06/11/JUC之AQS简介/
作者
jievhaha
发布于
2022年6月11日
许可协议