AQS
- 内部使用一个int变量state表示同步状态。
- 内部使用一个隐式的FIFO队列(并没有声明这样一个队列,只是通过每个节点记录它的上下节点来从逻辑上产生一个队列)来完成阻塞线程的排队。
这个FIFO队列在 AQS 中被定义为一个内部类Node:
- EXCLUSIVE、SHARED:
节点的两种模式:独占模式和共享模式,分别对应独占锁和共享锁。
- Thread:就是节点对应的线程。
- waitStatus:
- 取消状态CANCELLED。
- 通知状态SIGNAL。
- 条件阻塞状态CONDITION。
- 传播状态PROPAGATE。
- prev:前驱节点
- next:后继节点
- nextWaiter:
- 等待队列表示 后继节点
- 在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED 标识当前节点是独占模式还是共享模式。
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上,
* 当其他线程对Condition调用了signal()方法后,
* 该节点将会从等待队列中转移到同步队列中,
* 加入到对同步状态的获取中。
*/
static final int CONDITION = -2;
/**
* 传播状态,表示当前场景下后续的acquireShared能够得以执行。
*/
static final int PROPAGATE = -3;
/**
* 节点的的状态
* 初始状态为0 表示当前节点在sync队列中,等待着获取状态。
*/
volatile int waitStatus;
/**
* 前驱节点
*/
volatile Node prev;
/**
* 后继节点
*/
volatile Node next;
/**
* 获取同步状态的线程(当前节点线程)
*/
volatile Thread thread;
/**
* 等待队列表示 后继节点
* 在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED 标识当前节点是独占模式还是共享模式;
*/
Node nextWaiter;
/**
* 是否共享锁模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回前驱节点
*/
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;
}
}
同步队列的基本结构:
尾节点设置:
如果线程获取同步状态失败 线程会被构造进Node 中 然后加入到同步队列,加入到队列的过程必须是安全的因为可能有多个线程同时获取同步状态失败, AQS中使用基于CAS的方法加入队列:
/**
*
* @param expect 之前的队尾
* @param update 需要加入队列的节点
*/
compareAndSetTail(Node expect , Node update)
在一个Node被CAS设置为队列之前,这个Node的prev已经被设置为之前的尾节点,而在这个Node被设置为队尾之后,之前尾节点的next才会被指向这个Node。
首节点设置:
在队列中,首节点是当前获取同步状态成功的节点,首节点在释放同步状态时,会唤醒后继节点,而后继节点会在自己获取同步状态成功时,将自己设置为首节点。因为设置首节点是由持有同步状态的线程来完成的,因此不需要使用CAS来保证线程安全,只需要持有同步状态的线程将首节点设置为原首节点的后继节点并断开原首节点的next引用即可。
独占锁的获取和释放
线程A 进入代码块运行 当前没有其他线程线程A 获取到同步状态 调用了 acquire方法 获取到AQS的锁 |
新城 B 线程C 进入代码块 由于线程A 获取的同步状态 此时 线程B和C 无法获取 B和C 加入到 同步队列 |
线程A 运行完毕 完毕调用了 release方release方法唤醒同步队列中head节点线程 |
获取独占同步状态使用方法acquire(int arg):
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其中tryAcquire 是我们重写的方法(上一节有描述) 如果 tryAcquire 返回true 线程获得同步状态否则 运行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法线程阻塞并且加入同步队列.
- addWaiter 方法 加入到尾部队列
private Node addWaiter(Node mode) {
// 将当前线程构造为Node,mode传入值为Node.EXCLUSIVE (标识线程为独占模式 保存在 Node的nextWaiter字段)
Node node = new Node(Thread.currentThread(), mode);
// 如果当前尾节点不为空,尝试设置当前节点为尾节点,
// 如果当前不存在尾节点或者cas设置失败,调用完整设置尾节点enq方法。
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// cas 把当前节点设置为尾节点成功后 把之前尾节点的后继节点设置为当前尾节点
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果当前队列为null 没有尾节点 新建一个空节点作为首节点和尾节点 然后进入下一个for循环
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 不为null 则把当前节点设为尾节点,并将原尾节点next指向当前节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- acquireQueued方法 :判断是否轮到自己获取同步状态
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果当前节点的前驱是头节点,说明即将轮到自己获得同步状态,调用tryAcquire检查是否能获取到同步状态
if (p == head && tryAcquire(arg)) {
setHead(node); //获取到同步状态则将自己设为头节点
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果前驱节点已经是SIGNAL状态则前驱节点执行完成后会唤醒当前节点
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//前驱节点状态为CANCELLED,则继续查找前驱节点的前驱节点,因为当首节点唤醒时,会跳过CANCELLED节点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
//如果是0或PROPAGATE状态,则用CAS设置为SIGNAL
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//该方法在 shouldParkAfterFailedAcquire 方法返回true后执行,shouldParkAfterFailedAcquire 方法返回true代表前驱节点已经被设置为SIGNAL状态,
因此当前节点可以阻塞等待唤醒了,使用LockSupport.park(this)方法来阻塞。这个方法会一直阻塞直到首节点唤醒当前节点或当前节点被中断,如果是被中断,中断标识将会被一直往上层方法传,最终acquire方法会执行selfInterrupt。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
释放独占锁使用方法release(int arg):
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease方法是我们自己在AQS子类中重写的方法,可以看到release方法的逻辑比较简单,如果tryRelease方法返回false,那么release方法直接返回false;如果tryRelease方法返回true则通过unparkSuccessor方法唤醒后继节点:
private void unparkSuccessor(Node node) {
//如果头节点的状态是负值,将其归0.如果失败了也ok。
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//通常要唤醒的节点就是头节点的直接后继节点,但是如果直接后继节点是null或状态为CANCELLED,则从tail向前遍历取离head最近的一个非CANCELLED状态的节点。这里之所以要从tail向前遍历,前面说过原因:最后的tail节点在构造的时候在某时刻可能只有其向前一个节点的prev引用,而没有前一个节点向它的next引用。
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); // 唤醒下一个节点
}
共享式同步状态的获取和释放
共享式同步状态调用的方法是acquireShared:
public final void acquireShared(int arg) {
//获取同步状态的返回值大于等于0时表示可以获取同步状态
//小于0时表示可以获取不到同步状态 需要进入队列等待
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//和独占式一样的入队操作
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//自旋
for (; ; ) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//前驱结点为头节点且成功获取同步状态 可退出自旋
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//退出自旋的节点变成首节点
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
和独占式获取相比,主要区别在于setHeadAndPropagate方法:
一个节点在获取了同步状态后,不仅把自己设置为头节点,而且如果当前同步状态>0||原head为null||原head的状态<0||当前head为null||当前状态<0,且下一个节点的类型为null||下一个节点类型为shared,则继续唤醒下一个节点。
释放通过调用releaseShared方法:
public final boolean releaseShared(int arg) {
//释放同步状态
if (tryReleaseShared(arg)) {
//唤醒后续等待的节点
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
//自旋
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后续节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}