AQS简介
AQS是AbstractQueuedSynchronizer
的简称,即抽象队列同步器;是一个用来构建锁和同步器的框架。
AQS的数据结构
AQS内部使用了一个volatile的变量state来作为资源的标识,同时定义了几个获取和改版state的protected方法,子类可以覆盖这些方法来实现自己的逻辑:
getState();
setState();
compareAndSetState();
这三种操作均是原子操作,其中compareAndSetState
的实现依赖于Unsafe的compareAndSwapInt()
方法;
而AQS类本身实现的是一些排队和阻塞的机制,如具体线程等待队列的维护,内部使用了一个先进先出的双端队列,并使用两个指针head和tail用于标识队列的头部和尾部,**但是它不是直接储存线程,而是储存拥有线程的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的值,表示有资源可⽤,新head结点需要继续唤醒后继结点(共享模式下,多线程并
static final int PROPAGATE = -3;
// 等待状态,取值范围,-3,-2,-1,0,1
volatile int waitStatus;
volatile Node prev; // 前驱结点
volatile Node next; // 后继结点
volatile Thread thread; // 结点对应的线程
Node nextWaiter; // 等待队列⾥下⼀个等待条件的结点
// 判断共享模式的⽅法
final boolean isShared() {
return nextWaiter == SHARED;
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 其它⽅法忽略,可以参考具体的源码 }
// AQS⾥⾯的addWaiter私有⽅法
private Node addWaiter(Node mode) {
// 使⽤了Node的这个构造函数
Node node = new Node(Thread.currentThread(), mode);
// 其它代码省略 }
通过Node我们可以实现两个队列,一是通过prev和next实现线程同步队列、双向队列,而是nextWaiter实现Condition条件上的等待线程队列(单向队列)。
AQS的主要方法源码分析
AQS的设计是基于模板方法模板的,它有一些方法必须要子类去实现的,主要有:
isHeldExlusively()
:该线程是否正在独占资源,只有用到condition才需要实现;tryAcquire(int)
:独占方式,尝试获取资源,成功返回true,失败返回false;tryRelease(int)
:独占方式,尝试释放资源,成功返回true,失败返回false;tryAcquireShared(int)
:共享方式,尝试获取资源,负数表示失败,0表示成功,但没有剩余可用资源,正数表示成功,且有可用资源;tryReleaseShared(int)
:共享方式,尝试释放资源,如果释放后允许唤醒后续等待节点返回true,否则返回false。
获取资源
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
arg是要获取资源的个数,在独占模式下始终为1;
- 首先调用
tryAcquire(arg)
尝试去获取资源; - 如果获取资源失败,就通过
addWaiter(Node.EXCLUSIVE)
方法把这个线程插入到等待队列中,其中传入的参数代表插入的Node是独占的;
private Node addWaiter(Node mode) {
// ⽣成该线程对应的Node节点
Node node = new Node(Thread.currentThread(), mode);
// 将Node插⼊队列中
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 使⽤CAS尝试,如果成功就返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果等待队列为空或者上述CAS失败,再⾃旋CAS插⼊
enq(node);
return node;
}
// ⾃旋CAS插⼊等待队列
private Node enq(final Node node) {
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;
}
}
}
}
- 现在通过
addWaiter
方法,已经把一个Node放到等待队列尾部,而处于等待队列的结点是一个一个去获取资源的,acquireQueued的实现代码:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// ⾃旋
for (;;) {
final Node p = node.predecessor();
// 如果node的前驱结点p是head,表示node是第⼆个结点,就可以尝试去获取资源了
if (p == head && tryAcquire(arg)) {
// 拿到资源后,将head指向该结点。
// 所以head所指的结点,就是当前获取到资源的那个结点或null。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果⾃⼰可以休息了,就进⼊waiting状态,直到被unpark()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 结点进入等待队列后,是调用park使它进入阻塞状态的,只有头结点的线程是处于活跃状态的;
释放资源
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) {
// 如果状态是负数,尝试把它设置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 得到头结点的后继结点head.next
Node s = node.next;
// 如果这个后继结点为空或者状态⼤于0
// 通过前⾯的定义我们知道,⼤于0只有⼀种可能,就是这个结点已被取消
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);
}