AbstractQueuedSynchronizer (队列同步器)源码解析
结构
AbstractQueuedSynchronizer 是一个抽象的同步器,内部结构使用了双向链表(同步队列) + 单链表 (条件队列),在一般的情况下都只会使用同步队列,设计到阻塞队列 BlockQueue 时,会使用到条件队列,所以说 AbstractQueuedSynchronizer 是一个强大的抽象类,涵盖了并发时涉及到的各个方面。下面只会涉及到同步队列的使用
1. AbstractQueuedSynchronizer 结构
根据图片上可以看出,AbstractQueuedSynchronizer 使用了 head、tail 两个节点,然后实现了静态内部类 Node。
2. Node 结构
Node 的结构如下,主要的属性包括了 waitStatus(该节点或者后继节点的状态,后面使用 ws 作为简称)、thread(当前线程),通过将这两个属性作为参数构造 Node 节点。
3. 同步队列结构
根据图片可以看出,同步队列的结构中 head 节点中的 thread=null、ws=0,只是作为队列的队首。不参与实际的阻塞、唤醒操作。
重点方法
下面会根据获取锁、释放锁这两个方面去看AQS的整个流程。下面采用的是非公平模式。
lock (获取锁)
1. AbstractQueuedSynchronizer#acquire(int arg)
public final void acquire(int arg) {
/**
* 1. 首先尝试获取锁
* 2. 获取不到则会将当前线程作为参数封装成 node,进入到同步队列中
* 3. node 作为参数入队后,会再次尝试获取,获取不到时,则会阻塞
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg))
selfInterrupt();
}
2. tryAcquire(arg)
tryAcquire(arg)
方法需要由子类进行实现,因为不同模式下的尝试获取操作不一致,这里体现了模板方法的设计思想,例如非公平模式下,ReentrantLock.NonfairSync#tryAcquire(args):
1. 首先获取当前锁的状态 state,没有被持有 state = 0,否则为重入多少次
2. state = 0: 直接 CAS 设置当前锁被持有,并且设置该把锁的独占线程为当前线程
3. state != 0: 查看当前锁的持有线程是否是当前线程,如果是,那么 state + 1, 否则获取失败锁。
// 非公平模式下的 tryAcquire
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 当前锁的状态
int c = getState();
// 没有被持有,直接CAS设置该锁被当前线程持有
// 并设置独占线程 exclusiveOwnerThread 属性为当前线程
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 表示重入,增加重入次数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 获取失败,返回false
return false;
}
// 公平模式
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 表示队列中存在着非当前线程的节点,存在则返回true,获取失败
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
3. tryAcquire(arg)
tryAcquire(arg)
返回false,表示获取失败,继续执行acquireQueued(addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg))
,addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg)
表示将 node 添加到队列的尾部。
private AbstractQueueSync.Node addWaiter(AbstractQueueSync.Node mode) {
AbstractQueueSync.Node node = new AbstractQueueSync.Node(Thread.currentThread(), mode);
AbstractQueueSync.Node pred = tail;
// 如果队列不为空,那么直接 CAS 将当前构造后的节点入队,将 node 设置为 tail
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 走到这里表示,队列为空或者CAS失败,结果都是入队失败,那么继续将 node 进行入队操作
enq(node);
return node;
}
//初始化队列然后再次进行入队操作
private AbstractQueueSync.Node enq(final AbstractQueueSync.Node node) {
for (; ; ) {
AbstractQueueSync.Node t = tail;
// 尾节点为空,表示队列是空,那么需要初始化节点,也就是 head 节点
// 这里体现了懒加载的机制,当需要插入节点时,再进行初始化
if (t == null) {
if (compareAndSetHead(new AbstractQueueSync.Node()))
tail = head;
} else {
// 出现的情况为 tail 不为空,然后将 node 设置为尾节点,并将原来的
// tail 节点设置为 node 的前一个节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
将 node 节点添加进同步队列后,addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg)
返回 node,继续执行从同步队列中获取锁,返回结果为当前线程是否中断,如果为true,表示中断,那么会执行selfInterrupt();
,再次中断。
4. acquireQueued(final AbstractQueueSync.Node node, int arg)
acquireQueued(final AbstractQueueSync.Node node, int arg)
再次尝试从同步队列头部中获取锁,获取失败,则会执行将当前线程阻塞,并且检查中断状态。
final boolean acquireQueued(final AbstractQueueSync.Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (; ; ) {
// 1. 获取当前节点 node 的前驱节点 p
final AbstractQueueSync.Node p = node.predecessor();
// 2. 如果 p == head,那么当前线程再次尝试获取锁
if (p == head && tryAcquire(arg)) {
// 2.1 当前线程获取锁成功,那么就会将 head 指向 node,并且将
// head 的属性值都初始化,保证 head 节点不存在实际的属性值
setHead(node);
p.next = null; // help GC
failed = false;
System.out.println(Thread.currentThread().getName() + "成功被唤醒");
return interrupted;
}
/**
*
* 3. 走到这里,表示 p != head 或者 尝试获取线程失败,
* 那么需要将 p 节点的 waitStatus 状态修改为后继节点等待被唤醒 SIGNAL,接着将 node 指向的当前线程给阻塞
* shouldParkAfterFailedAcquire: 这里需要进入两次这个方法设置 waitStatus 值,因为初始值为 0, 所以该方法
* 会返回 false,那么继续自旋,等到下一步这里时, ws 的值已经被设置为 SIGNAL = -1,那么会返回 true,接着再
* 阻塞当前线程。为什么会进入两次判断呢,因为在 unlock 时,会唤醒线程,接着将队列中的唤醒节点 node 作为 head,并使得
* ws = 0,等到下一次其他线程获取时,可以保证 ws = 0。不用再去其他方法中修改 ws 值。
*/
if (shouldParkAfterFailedAcquire(p, node) &&
// 将当前线程阻塞
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) {
// 当线程中断时,需要取消 node 节点获取
System.out.println("线程中断,取消当前线程获取" + Thread.currentThread().getName());
cancelAcquire(node);
}
}
}
5. shouldParkAfterFailedAcquire(p, node)
设置前驱节点的 ws 状态为SIGNAL。如果是第一次进入该方法时,如果 prev 的 ws 不为 SIGNAL,那么返回 false,并将ws 设置为SIGNAL,继续进行自旋,再次判断队列中 node 节点的前驱节点为 head,如果是,那么会再次进行尝试获取锁,否则进入 shouldParkAfterFailedAcquire
,因为第一次已经将 ws 设置为 SIGNAL,那么这一次会返回true。接着执行 parkAndCheckInterrupt()
阻塞当前线程。
private static boolean shouldParkAfterFailedAcquire(AbstractQueueSync.Node pred, AbstractQueueSync.Node node) {
// pred 节点的 ws
int ws = pred.waitStatus;
if (ws == AbstractQueueSync.Node.SIGNAL)
// 直接返回
return true;
// 当前 ws = cancelled,循环从后向前遍历,跳过 cancelled 的节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/**
* 因为当前 node 需要被唤醒,所以如果 ws 不是 cancelled 的节点,那么设置 pred 的 ws 为唤醒
*/
compareAndSetWaitStatus(pred, ws, AbstractQueueSync.Node.SIGNAL);
}
return false;
}
6. parkAndCheckInterrupt()
parkAndCheckInterrupt()
: 阻塞当前线程并返回线程的中断状态。
private final boolean parkAndCheckInterrupt() {
System.out.println(Thread.currentThread().getName() + "阻塞,等待被唤醒");
// 当前线程阻塞
LockSupport.park(this);
// 返回当前线程是否中断状态
return Thread.interrupted();
}
unlock (释放锁)
1. release(int arg)
release(int arg)
: 释放锁,tryRelease(arg)
也是一个抽象方法,交给子类实现,可以看下ReentrantLock#tryRelease(int releases)
public final boolean release(int arg) {
System.out.println(Thread.currentThread().getName() + "开始释放锁");
if (tryRelease(arg)) {
// 释放成功
AbstractQueueSync.Node h = head;
// h.waitStatus 为 0,表示为初始状态,这里是不是初始状态
if (h != null && h.waitStatus != 0) {
// 唤醒 head 节点的一个后继节点
unparkSuccessor(h);
}
return true;
}
return false;
}
2. tryRelease(int releases)
tryRelease(int releases)
:尝试释放锁,当前锁的重入数 state - 1,首先判断锁的持有线程是否为当前线程,然后判断锁的重入次数是否已经为0,如果为0,表示锁已经完全释放,设置锁的状态,返回 true,否则返回 false。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 判断当前线程是否和锁的持有线程相等,如果不相等,表示这个锁出现了问题,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
// 锁释放,设置独占线程为 null
setExclusiveOwnerThread(null);
}
// 设置 state = state - 1,state 不为0,则返回true,继续被持有线程持有,释放不完全
setState(c);
return free;
}
3. unparkSuccessor(h)
unparkSuccessor(h)
:从同步队列 head 节点后第一个状态正常的节点进行唤醒。
/**
* 唤醒 node 的后继节点,正常进入此处为 head 节点,除了中断操作外
* @param node the node
*/
private void unparkSuccessor(AbstractQueueSync.Node node) {
/*
* ws < 0, 表示后继节点需要被唤醒, 然后将 CAS 将 ws 置为初始值 0,
* 设置 ws 为 0,因为 node 的 ws < 0 表示的是node 的下一个节点可以被唤醒,
* 那么就先要将当前 node 的 ws 状态进行修改, 方便节点被唤醒后,在方法
* #acquireQueued(final AbstractQueueSync.Node node, int arg)中不需要设置前驱节点 p 的 ws = 0,帮助GC回收
*/
int ws = node.waitStatus;
// ws < 0: 表示不是要被取消的节点
if (ws < 0) {
compareAndSetWaitStatus(node, ws, 0);
}
/*
* 这里 s 节点为需要被唤醒的节点,也就是为 node 的第一个后继正常状态的节点
* 将等待状态为 cancelled 的节点都进行出队
* ws > 0:CANCELLED = 1: 表示状态为取消,
* 后面的意思为:找到 node 的后继节点 s ,如果 s 不是被标识为取消,那么就将 s 对应的线程唤醒
* 如果是 null 或者为取消状态,那么就从 tail -> node 中,找到离 node 最近的一个不是取消状态的节点,然后将该节点对应的线程唤醒
*/
AbstractQueueSync.Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从等待队列的尾部进行过滤
for (AbstractQueueSync.Node t = tail; t != null && t != node; t = t.prev) {
// waitStatus <=0 的状态有 SIGNAL = -1; CONDITION = -2;PROPAGATE = -3;
if (t.waitStatus <= 0) {
s = t;
}
}
}
if (s != null) {
// 唤醒 s.thread 节点
LockSupport.unpark(s.thread);
System.out.println(s.thread.getName() + "节点被唤醒");
}
}
上述的流程可以理解为 lock 与 unlock 操作之间的一个交替行为,当锁被持有时,需要 unlock 释放锁,然后唤醒阻塞线程继续执行。
测试
手动将ReentrantLock
类和AbstractQueuedSynchronizer
复制一份到本地,进行调试,然后使用5个线程去争夺锁,观察打印的结果。
模拟非公平锁的获取锁和释放锁的过程
package com.rule.concurrent.aqs;
public class AQSDemo {
private static ReentrantLock reentrantLock = new ReentrantLock(false);
public static void toThisMethod() {
System.out.println(Thread.currentThread().getName() + "开始进入该方法");
reentrantLock.lock();
// 模拟业务代码执行
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() + "开始执行结束该方法");
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// 5个线程去争夺锁
new Thread(AQSDemo::toThisMethod, "thread-" + i).start();
}
}
}
执行结果
同步队列中的顺序 3->4->2->1,然后释放锁的顺序为 0->2->1->3->4,这种情况就反应出了不公平的线程,同步队列中的入队顺序不是最后释放锁的顺序。
模拟公平锁的获取锁和释放锁的过程
执行结果
同步队列中的顺序 1->3->2->4,然后释放锁的顺序为 0->2->1->3->4,这种情况就反应出了不公平的线程,同步队列中的入队顺序不是最后释放锁的顺序。