AbstractQueuedSynchronizer 源码解析
前言
本文主要从源码角度解析AQS(AbstractQueuedSynchronizer)的各个方法(之后文章中统一简称AQS)。
1. AQS的基本结构
AQS是一个构建锁和其他同步组件(如:ReentrantLock,CountDownLatch,Semaphore…)的基础框架。它的父类是AbstractOwnableSyncrhonizer,里面包含了2个方法:setExclusiveOwnerThread(Thread thread)和getExcelusiveOwnerThread()。set方法用来设置资源独占线程,get方法用来获取最近一次被set方法设置的线程。资源独占线程的意思是当一个线程获取到资源后,给这个资源上锁,其他线程在获取该资源的线程释放掉锁之前,无法再获取该资源。
1.1 AQS的大致流程
加锁过程概述:
首先会调用AQS中的acquire方法里的tryAquire方法进行加锁(tryAcquire方法实现的方法有公平锁和非公平锁2种)。
- 如果获取成功,state加1并且通过父类方法AbstractOwnableSynchronizer的set方法设置资源独占线程。
- 如果获取失败,说明前面已经有线程占用了该资源,所以当前线程需要进行排队等待资源释放。所以AQS将当前线程封装成为一个Node类对象放入双向链表中,之后通过Locksupport.park()阻塞当前线程。当他被排在他前面的那个线程唤醒之后,他将继续调用tryAcquire方法进行加锁操作,知道获得该资源,并把自身node节点设为头节点,并把之前的头节点去掉。
解锁过程概述:
通过调用AQS中的release方法里的tryRelease方法解锁的同时唤醒下一个有效的后继节点。
1.2 AQS维护了一个FIFO的双向链表以及一个state属性变量
AQS的核心思想是当一个线程尝试获取资源时,如果资源处于闲置的状态,则将该线程设置为工作线程,并且将该资源设置为锁定状态。在资源处于锁定状态时,如果有其他线程来尝试获取资源,则会通过一个队列的形式将这些线程进行阻塞排队。实现这个排队队列的数据结构是基于CLH(Craig, Landin, and Hagersten)单向队列锁的变种来实现的一个双向,FIFO的双向链表锁(CLH相关实现不是本文的重点,大家有兴趣可以自行查阅相关资料和文档)。
下面我们来看看AQS维护的这个链表的结构与实现:
如果线程获取资源: 调用acquire(1)方法,acquire会调用tryAcquire(1)的抽象方法尝试获取资源。tryAcquire实现大致分为2中:公平锁获取,非公平锁获取(具体区别会在后面的文章中提到)。
- 如果获取成功:state属性加1,并通过父类中的setExclusiveOwnerThread方法设置当前线程为该资源的资源独占线程。
- 如果获取失败:说明之前已经有线程抢占了该资源,该线程则会进行排队。AQS会把该线程封装成Node对象放入存放Node的双向队列中,然后通过park()方法阻塞当前线程。之后该节点会一直处于等待睡眠状态,直到它上之前的节点(通常为它上一个节点)通过unpark方法把它唤醒。这时,它会循环调用tryAcquire()方法尝试获取资源,直到获取到资源将自身节点设置为head节点。如果获取失败则继续排队。
如果线程释放资源:调用release方法,release方法调用tryRelease()方法尝试释放资源,如果释放成功state减1,如果此时state为0,通过父类的setExclusiveOwnerThread(null)方法将资源独占线程设为0,再通过unpark()方法唤醒下一个节点的线程。值得注意的是tryRelease()方法只在Sync类中实现,所以解锁的过程并不区分公平锁与非公平锁。
1.3 AQS维护了一个ConditionObject单向链表内部类
ConditionObject提供了条件锁的同步条件,实现了Condition接口。主要作用是为了并发编程中的同步提供了等待通知的实现。当线程不满足某些条件是,可以让线程挂起等待,直到满足条件时被AQS双向链表中的某个节点唤醒并进入AQS双向队列中。
2.AQS源码
2.1 成员变量
/**
* 设置用来支持CAS。这里我们需要通过native方法来实现:为了允许之后能够进行增强,
* 我们不能明确的继承AtomicInteger,虽然这回更高效和有用。所以,我们本地实现hotspot
* 内部的API。
*/
//以下属性都用于cas操作
//unsafe工具类对象
private static final Unsafe unsafe = Unsafe.getUnsafe();
//state变量的内存偏移量
private static final long stateOffset;
//head节点变量的内存偏移量
private static final long headOffset;
//tail节点变量的内存偏移量
private static final long tailOffset;
//Node节点的waitStatus属性的内存偏移量
private static final long waitStatusOffset;
//Node节点的next属性的内存偏移量
private static final long nextOffset;
//静态代码块初始化上述属性
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
/**
* CAS head field. Used only by enq.
*/
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
/**
* CAS waitStatus field of a node.
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
/**
* CAS next field of a node.
*/
private static final boolean compareAndSetNext(Node node,
Node expect,
Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
2.2 Node内部类
static final class Node {
/** 表示一个在共享模式中处于等待状态的标识 */
static final Node SHARED = new Node();
/** 表示一个在独享模式下处于等待状态的标识 */
static final Node EXCLUSIVE = null;
/** waitStatus的值,表示该线程已经被取消 */
static final int CANCELLED = 1;
/** waitStatus的值,表示后继线程需要被唤醒 */
static final int SIGNAL = -1;
/** waitStatus的值,表示线程处于条件等待状态 */
static final int CONDITION = -2;
/** waitStatus的值,表示下一次执行acquireShared方法需要无条件的传播(
只有当线程处于SHARED状态时,才会使用该字段) */
static final int PROPAGATE = -3;
/**
* Status属性, 只能为以下这些值:
* SIGNAL: 这个节点的后继节点被(或者马上将被park方法)阻塞,所以当前节点
* 必须在他释放锁或者被取消时唤醒他的后继节点。为了避免竞争,aquries方法
* 必须首先声明他们需要一个signal,然后重试原子的aquire方法,如果失败,则
* 阻塞。
* CANCELLED: 这个节点因为超时或者中断而被取消。节点进入该状态后不会在变化。
* 特别是,当一个线程处于被取消的节点后不会再被阻塞。
* CONDITION: 这个节点正处于一个条件队列里。除非它的状态被置为0并
* 被转移到同步队列里,否则它不会被当成一个同步队列节点
* PROPAGATE: 一个releaseSHared需要被传播给其他节点。
* 这个值在doReleaseShared中被设置(仅仅对于头节点)来确保继续传播,
* 0: 初始状态
*
*/
/**
* 表示【当前节点在队列中的状态】
*
* 这个属性在正常的sync节点里被初始化为0,在condtion节点里被设置为CONDITION。
* 这个属性是通过CAS进行修改的(或者在可能的情况下,无条件的volatile写入)。
*/
volatile int waitStatus;
/**
* 【前驱指针】
*
* 指向当前节点/线程所依赖的先驱节点来检查waitStatus。
* 在入队的过程中进行设置,在出队之前被置为空(为了GC)。
* 并且,在先驱节点被取消前,我们会在找到一个未被取消的节点时执行短路操作,
* 因为头结点一直存在不会被取消,所以这个操作将一直存在。
* 一个节点只在成功的acquire后成为头节点。
* 一个取消的线程永远不会成功获取,并且一个线程只能取消它自己。
*/
volatile Node prev;
/**
* 【后继指针】
*
* 指向当前节点/线程的后继节点,当前线程即将释放时将其唤醒。
* 在入队时设置,当绕过取消的前驱节点时进行调整,在出队是置为空(为了GC)。
* 因为直到附加操作前入队操作没有赋值给下一个前驱节点的属性,
* 所以看到一个空的next节点的属性不一定代表这个节点在这个队列的末尾。、
* 但是,如果一个next节点的值为空,我们可以通过从尾部向前扫描来double check一下。
* 一个被取消的next节点被设置为指向自己而不是置为空,
* 这样使得isOnSyncQueue方法判断起来更加容易
*/
volatile Node next;
/**
* 【表示处于该节点的线程】
*
* 当前节点中入队的线程。在构造函数中初始化,在使用后被置为空
*/
volatile Thread thread;
/**
* 表示【下一个处于CONDITION状态的节点】
*
* 指向下一个在Condition队列里的等待中的节点,或者特殊值SHARED。
* 因为Condition队列只有在被独占模式持有时才能访问,
* 所以在他们在Condition队列中等待时,
* 我们只需要一个简单的链表队列来储存他们。
* 他们随后被转移到一个队列中来重新获取。
* 而且因为conditions只能是独占,我们用特殊值来表示共享模式并储存在属性里。
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
* 如果节点在共享模式中处于等待状态返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
* 返回上一个节点,如果为空则抛出空指针异常。
* 当predecessor不能为空时使用。
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
2.3 获取锁相关方法
/**
*
* 独占模式获取,忽略打断。通过至少调用一次tryAcquire()方法来实现。如果成功,
* 直接返回。否则线程将排队,在调用tryAcquire方法成功前可能反复的阻塞与解阻塞。
* 这个方法可以被用来实现Lock类中的lock方法。
*
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
*
* 在当前线程和指定模式下创建一个节点并且入队
*
*/
private Node addWaiter(Node mode) {
//将当前线程和指定模式封装成一个Node对象
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//入队分为以下两种情况:
//1.队尾不为空,
//则说明Node链表已经被初始化,
//直接通过compareAndSetTail方法将新的node
//加在队尾即可
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//2.队尾为空,
//则说明Node链表还没有被初始化,
//通过enq方法初始化Node链表并将自己加在队尾
enq(node);
return node;
}
/**
*
* 将一个节点入队,如果没有tail则初始化
*
*/
private Node enq(final Node node) {
//自旋cas,防止其他线程已经初始化了这个链表
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//初始化一个空节点作为链表的头
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//入队
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
*
* 以不间断独占模式获取已经在队列里的线程。
* 用于Condtion wait方法和acquire方法
*
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的上一个节点
final Node p = node.predecessor();
//这里分为2种情况:
// 1. 当前节点的上一个节点为头节点,进行下一步尝试获取同步
// 2. 当前节点的上一个节点不是头节点,排队
if (p == head && tryAcquire(arg)) {
setHead(node);
//能进入该方法说明tryAcquire方法获得了同步,并加锁成功,
//说明当前节点的上一个节点已经完成任务并释放掉锁了,
//直接将其置为空帮助GC
p.next = null; // help GC
failed = false;
return interrupted;
}
//进行到这里,说明上一个条件没有成立
//分2种情况:
// 1. 当前节点的上一个节点不是头节点
// 2. 当前节点的上一个节点是头节点但是tryAcquire尝试获取同步失败
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
*
* 检查并更新没能成功获取同步的节点的状态。如果线程应该被阻塞返回true。
* 这个是所有acquire循环的主要信号控制。
*
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
*
* 如果ws==Node.SIGNAL,说明前驱节点已经设置状态要求通知下一个节点,
* 所以他的后继节点可以安全的park。
*
*/
return true;
if (ws > 0) {
/*
*
* 如果ws>0,说明前驱节点的前驱节点取消了。
* 设置前驱节点为原前驱节点的上一个节点,
* 知道新的前驱节点的状态不为CANCELLED。
*
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
*
* 其他情况,waitStatus 一定等于0或者PROPAGATE。
* 表明我们需要一个signal,但是暂时不要park。
* 调用者需要再试一次,确保他不能获取同步之后再进行park。
*
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
*
* park并且检查线程是否被中断
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
/**
* 取消节点的获取状态
*
*/
private void cancelAcquire(Node node) {
//如果节点不存在,直接返回
if (node == null)
return;
// 将该节点的线程置空
node.thread = null;
// 跳过被取消的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取过滤后的前驱节点的后继节点
Node predNext = pred.next;
// 将该节点的状态置为CANCELLED
node.waitStatus = Node.CANCELLED;
// 如果节点为tail节点,直接之前获取的过滤后的前驱节点的后继节点置为尾节点
//如果CAS成功,将tail的后继节点置为空
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
//如果失败
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
// 如果当前节点的前驱节点不是head节点
// 继续判断
// 1.当前节点的前驱节点的waitStatus是否为SIGNAL
// 2.尝试把当前节点的前驱节点置为SIGNAL状态看是否成功
// 如果1,2有一项返回true,则把当前节点的前驱节点的线程置为空
// 如果以上判断条件返回true,把当前节点的前驱节点的next指针指向当前节点的后继节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
}
//如果当前节点的前驱节点是head节点,或者以上条件都不满足,唤醒当前节点的后继节点
else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
2.4 释放锁相关方法
/**
*
* 在独享模式中释放资源。如果tryRelease返回true通过unblocking1个或多个线程来实现
* 这个方法可以被用来实现Lock类中的unlock方法。
*
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//如果头节点不为空,并且头节点的等待状态不是默认状态
//唤醒他的后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
*
* 唤醒指定节点的后继节点(如果存在的话)
*
*/
private void unparkSuccessor(Node node) {
/*
*
* 如果waitStatus为负数(比如SIGNAL)尝试清除预期的信号。
* 如果这个操作失败或者状态被等待中的线程改变也没有关系。
*
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 线程将要唤醒的节点在他的后继节点中,通常是下一个节点。
* 但是如果下一个节点被取消或者为空时,从tail往前找知道找到真实的
* 没有被取消的后继节点
*/
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;
}
if (s != null)
LockSupport.unpark(s.thread);
}