什么是AQS(AbstractQueuedSynchronizer)
1、字面意思:抽象队列的同步器,是用来构建锁或者其它同步器组件重量级基础框架以及整个JUC体系的基石,它通过内置FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量来表示持有锁的状态 ReentranLock Countdowlatch等都是通过AQS来实现的。
2、锁=面向锁的使用者,定义了程序员和锁交互的使用API,隐藏了实现细节,调用即可
3、同步器=面向锁的实现者,统一的规范并简化了锁的实现,屏蔽了同步状态管理,阻塞线程排队通知、唤醒机制等
4、加锁会导致阻塞,有阻塞就需要排队,实现排队必然须要有某种形式的队列来进行管理,抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然涉及一种排队等候的机制,抢占资源失败的线程会继续去等待,但是等候线程任然保留获取锁的可能且获取锁的流程任在继续
5、排队等候机制 由某种队列形成,队列是什么样的数据接口呢?如果共享资源被占用,就需要一定的阻塞等待唤醒机制保证锁分配,这个机制主要用的是CLH队列的变体来实现,将暂时获取不到的线程加入到队列中,这个队列就是AQS的抽象表现,它将请求共享资源的线程封装成队列的节点(Node),通过CAS自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的控制效果
AQS原理探究
1、AQS体系架构
2、AQS使用一个volatile的int类型的成员变量,来表示同步状态。通过内置FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改
3、AQS的同步状态state成员变量:
4、AQS的CLH队列,CLH(Craig、Landin、Hagersten)队列为一个双向队列,通过自旋等待(无CPU切片消耗),通过state变量判断是否阻塞
5、有阻塞就需要排队,实现排队必然需要队列,AQS=state变量+CLH变种的双端队列
6、AQS的队列Node类基本结构
// AQS 队列的Node类
static final class Node {
// 共享
static final Node SHARED = new Node();
// 独占
static final Node EXCLUSIVE = null;
// 线程被取消
static final int CANCELLED = 1;
// 后续线程需要唤醒
static final int SIGNAL = -1;
// 等待 condition唤醒
static final int CONDITION = -2;
// 共享模式同步状态获取将会无条件传播下去
static final int PROPAGATE = -3;
/**
* 表示上面的状态 初始化为0
* CANCELLED 1 表示线程获取锁的请求被取消
* CONDITION -2 表示节点在等待队列钟过,节点队列等待被唤醒
* PROPAGATE -3 当前线程处于 SHARED(共享)模式下,该字段才会使用
* SIGNAL -1 表示线程已经准备好了,就等资源释放了
*/
volatile int waitStatus;
// 前驱指针
volatile Node prev;
// 后续指针
volatile Node next;
//处于该节点的线程
volatile Thread thread;
}
// ReentrantLock 的构造函数true为公平锁,false为非公平锁,默认为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 内部Sync类,继承了 AbstractQueuedSynchronizer 非公所以及公平锁就继承它
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 摹本lock抽象方法
abstract void lock();
接下来再来看看公平锁与非公平锁的结构
在这里都看到 FairSync与NonfairSync都继承了Sync类
1、NonfairSync.lock()方法发什么了什么?
lock()方法
// 非公所的lock方法
final void lock() {
if (compareAndSetState(0, 1))
// 此方法来自于AQS的父类 AbstractOwnableSynchronizer类,表示谁持有这把锁,
// AbstractOwnableSynchronizer中维护一个Thread的成员变量
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
设置AQS的同步状态,也是刚刚上面提到的 private volatile int state; 如果此值为0代表为空闲(默认为0),状态大于0代表有线程正在执行,如果期望值是0,将其更新为1,返回ture,这时当前线程将会state更新成1,假如当前线程是A线程在做此操作并且成功(表示当前线程获取锁成功),那么B线程进入也会做compareAndSetState()操作,也即B线程想把预期值更新成期望值,如果当A线程没结束,那么B线程的预期值是0,但是A没结束(当前值是1),B线程将更新失败,所以更新将返回false那么B线程将会调用acquire()方法
// AQS类中compareAndSetState方法,实际上它调用的unsafe类的compareAndSwapInt方法,
// 它是一组原子操作(CAS),表示将预期值更新成期望值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
acquire()方法
//上面传过来的参数是1,acquire调用了 tryAcquire acquireQueued addWaiter selfInterrupt
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1、 tryAcquire()方法
// 非公平锁中重写的父类Sync的tryAcquire()方法,继承自AQS
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
1.1继续跟踪nonfairTryAcquire()方法
// nonfairTryAcquire() 是Sync类中的实现,也即 NonfairSync 的父类
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程,假设当A线程(state=1)持有锁在执行,当前B线程调用此方法
final Thread current = Thread.currentThread();
// 获取AQS中的state状态,注意此状态不是线程的状态,而是AQS中的state状态
// B线程获取的到state是1,也可能是0,因为A线程可能就在这时释放了锁
int c = getState();
// 这里判断A线程有没有执行完成
if (c == 0) {
// 如果A执行完了,那么B线程将再次调用compareAndSetState()获取尝试获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 判断当前线程与正在执行的线程是不是同一个(可重入锁的体现)
else if (current == getExclusiveOwnerThread()) {
// 如果是,则加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
2.AQS中的addWaiter()方法跟踪 假设tryAcquire()返回false那么进入addWaiter()方法,入队等候
注意条用在acquire()调用的addWaiter(Node mode) 入参是 Node.EXCLUSIVE(独占、排它)是个null
private Node addWaiter(Node mode) {
// 创建当前线程的Node节点
Node node = new Node(Thread.currentThread(), mode);
// 如果B线程是第一次进来(也即初始化),那么 tail== null,调用下面enq方法完成初始化双向队列
Node pred = tail;
if (pred != null) {
// 当B调用enq()方法完成初始化后
// 假设C进来
// 如果tail != null(尾指针不为null) 说明完成了队列的初始化,则不再调用enq()方法
// 将C节点node.prev节点指向尾节点,当前的尾节点为B所以C的prev指向的B
node.prev = pred;
// 将尾指针的指向为C也即C就是尾指针每个线程都按照此流程继续
if (compareAndSetTail(pred, node)) {
// 当期的pred为B节点 尾指针的Next(B节点) 指向C(尾部节点)
pred.next = node;
// C的addWaiter结束
return node;
}
}
enq(node);
return node;
}
//AQS中的enq方法
private Node enq(final Node node) {
// 自旋第一次
for (;;) {
// 获取尾指针
Node t = tail;
// 初始化为null
if (t == null) { // Must initialize
// 初始化尾指针
//compareAndSetTail(): tailOffset就是头指针的内存偏移量,
//private final boolean compareAndSetHead(Node update)
// return unsafe.compareAndSwapObject(this, headOffset, null, update);
//相当于new Node()的对象地址赋给了AQS的成员变量 private transient volatile Node head;
if (compareAndSetHead(new Node()))
// 第一次循环结束后,初始化完成头指针后,将头指针赋值给tail 组成双向队列,此时初始化链表结束,
// 一般这个头节点我们称为哨兵节点用来占位以及统一管理后续的操作状态
tail = head;
// 但是B线程的Node节点 还没初始化,也没有退出循环的条件,所以开启第二次循环
// 第二次循环已经完成了tail的初始化
// 所以进入了else分支
} else {
// B节点入队B节点的上个指针指向尾指针
node.prev = t;
// 比较并设置,将尾指针设置成B节点
if (compareAndSetTail(t, node)) {
// 将哨兵节点(头节点)的next指向B节点
t.next = node;
// 初始化完成
return t;
}
}
}
}
图解enq()方法初始队列:
如果已经调用enq方法完成初始化其它线程线程进入addWaiter()方法后:
3.调用 addWaiter()结束后(即入队结束)进入AQS的acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) {
// 假设当前执行完addWaiter()是B节点
// 取消排队标记
boolean failed = true;
try {
// 线程中断标记
boolean interrupted = false;
// 自旋
for (;;) {
// 获取prev节点,假设是B,B的prev就是哨兵节点
final Node p = node.predecessor();
// p == head 为true 继续 tryAcquire()尝试获取锁, 这时候如果其它线程还没释放锁(假设为A)则返回false
// 如失败调用shouldParkAfterFailedAcquire()以及parkAndCheckInterrupt()
if (p == head && tryAcquire(arg)) {
// 将当前节点设置成head节点
setHead(node);
// 将head节点next置为null以便GC
p.next = null; // help GC
// 设置排队的状态
failed = false;
return interrupted;
// 这里结束后,当前节点就变成了head节点,也即哨兵节点,哨兵节点出队,将等待被GC
}
// shouldParkAfterFailedAcquire 判断是不是应该挂起,parkAndCheckInterrupt挂起线程
// 如果挂起并不代表当前自旋就结束了,如果被唤醒那么一定是又线程释放锁了才会被唤醒,继续自旋获取锁,
// 这时应该就可以tryAcquire()占有锁;
// 如果被挂起,就要等待被唤醒,只有释放锁的时候才会被唤醒,流程走到这,从入队再到等候就基本结束了.
// 只要资源没有被释放线程将会一直被挂起,等待被唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 取消排队
if (failed)
cancelAcquire(node);
}
}
//AQS中的setHead()方法
private void setHead(Node node) {
// 将head 这是当前node
head = node;
node.thread = null;
node.prev = null;
}
//AQS类中Node predecessor 方法
final Node predecessor() throws NullPointerException {
// 获取当前节点的prev节点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//AQS中的shouldParkAfterFailedAcquire判断当前线程应不应阻塞
// pred 上个节点锁持有的状态(假设为B那么它的上个节点就是哨兵节点),node当前线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱节点的状态
int ws = pred.waitStatus;
// 如果时SIGNAL(-1)状态,即等待被占用的资源释放,直接返回true,准备继续调用parkAndCheckInterrupt()方法
// 判断否需要被唤醒,当二次进来时返回true,表示需要被阻塞
if (ws == Node.SIGNAL)
return true;
// ws > 0 说明时cancelled状态
if (ws > 0) {
// 循环判断前驱节点状态是否也为cancelled,忽略该状态的节点,直接重连队列
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 上个节点锁持有的状态 waitStatus 的值为 -1 表示需要被唤醒
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 阻塞线程
private final boolean parkAndCheckInterrupt() {
// 调用LockSupport.park()阻塞,等待唤醒
LockSupport.park(this);
// 返回线程的中选标记
return Thread.interrupted();
}
4.解下来走释放锁的unlock()方法
// ReentrantLock Sync的unlock()方法
public void unlock() {
// 调用了AQS的release()方法
sync.release(1);
}
// AQS的release方法
public final boolean release(int arg) {
// 调用子类实现的tryRelease()
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// Sync类重写的tryRelease方法
protected final boolean tryRelease(int releases) {
// 设置状态。如果当前state = 1 那么 状态将置为0
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 空闲状态
boolean free = false;
// 如果状态为0 表示空闲
if (c == 0) {
// 设置空闲状态
free = true;
// 将当前占用的线程设置为null
setExclusiveOwnerThread(null);
}
// 设置AQS state的状态
setState(c);
return free;
}
// AQS的unparkSuccessor方法,唤醒等待节点,并使节点出队
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
// 将waitStatus设置成0
compareAndSetWaitStatus(node, ws, 0);
// 获取下个节点 如果是A释放资源,那么此时进来的头节点就是哨兵节点,哨兵节点下个节点就是B节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒线程,然后唤醒的线程将会acquireQueued中自旋中继续尝试占有锁
if (s != null)
LockSupport.unpark(s.thread);
}
流程到这里基于非公平锁的以lock()方法为切入点的AQS源码分析基本结束了,不得不说AQS的流程处理真的非常精彩