AQS主要通过独占式、共享式同步状态的获取和释放,接下来我们来看下AQS代码是如何实现的
在AQS中,是使用队列的方式来实现同步管理的,我们先来认识下队列中的Node节点数据结构
static final class Node {
/**
* 共享模式节点
*/
static final Node SHARED = new Node();
/**
* 独占模式节点
*/
static final Node EXCLUSIVE = null;
/**
* CANCELLED值,表明线程已取消
*/
static final int CANCELLED = 1;
/**
* SIGNAL值,表明后续线程需要释放
*/
static final int SIGNAL = -1;
/**
* CONDITION值,指示线程正在等待条件
*/
static final int CONDITION = -2;
/**
* PROPAGATE值,指示下一个acquireShared应该无条件传播
*/
static final int PROPAGATE = -3;
/**
* 等待状态
*/
volatile int waitStatus;
/**
* 上一个节点
*/
volatile Node prev;
/**
* 下一个节点
*/
volatile Node next;
/**
* 进入该节点队列的线程。构造时初始化,使用后为空。
*/
volatile Thread thread;
/**
* 条件队列中的后续节点,如果当前节点是共享的,那么这个字段将是一个SHARED变量,
* 也就是说节点类型(独占和共享)和条件队列中的后续节点共用同一个字段
*/
Node nextWaiter;
/**
* 是否为共享模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回当前节点的prev node
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null) {
throw new NullPointerException();
} else {
return p;
}
}
Node() { // 用于建立初始头或共享标记
}
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节点中包含:
thread
:一个等待获取同步状态的线程prev
:指向上一个节点的引用next
:指向下一个节点的引用waitStatus
:等待的状态
nextWaiter
:表示条件队列中的后续节点,如果当前节点是共享的,那么这个字段将是一个SHARED变量static final Node SHARED = new Node();
,也就是说节点类型(独占和共享)和条件队列中的后续节点共用同一个字段acquire 获取同步状态方法入口
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 设置当前线程的中断标识
selfInterrupt();
}
}
上述代码主要完成了同步状态获取、节点构造、加入同步队列以及在同步队列自旋等待的相关工作,主要流程如下:
首先调用子类的tryAcquire
方法,该方法是线程安全的获取同步状态,子类实现
如果获取失败,通过addWaiter方法构造独占式同步节点并将该节点加入到同步队列的尾部
/**
* 为代表当前线程并指定模式为mode的节点创建/进入队列。
*/
private Node addWaiter(Node mode) {
// 将当前线程构造成Node节点
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速在尾节点后新增节点 提升算法效率 先将尾节点指向pred
Node pred = tail;
if (pred != null) {
// 尾节点不为空 当前线程节点的前驱节点指向尾节点
node.prev = pred;
// 并发处理 尾节点有可能已经不是之前的节点 所以需要CAS更新
if (compareAndSetTail(pred, node)) {
// CAS更新成功 当前线程为尾节点 原先尾节点的后续节点就是当前节点
pred.next = node;
return node;
}
}
//第一个入队的节点或者是尾节点后续节点新增失败时进入enq
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
//尾节点为空 第一次入队 设置头尾节点一致 同步队列的初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
//所有的线程节点在构造完成第一个节点后 依次加入到同步队列中
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();
//前驱节点为头节点且成功获取同步状态
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);
}
}
再来看看shouldParkAfterFailedAcquire
和parkAndCheckInterrupt
方法是如何阻塞当前线程的
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的状态决定后续节点的行为
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 前驱节点为-1 后续节点可以被阻塞
*/
return true;
if (ws > 0) {
/*
* 前置节点被取消了,跳过前置节点并继续重试,直到前置节点waitStatus<0,也就是不是取消状态的。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 前驱节点是初始或者共享状态就设置为-1 使后续节点阻塞
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//阻塞线程
LockSupport.park(this);
return Thread.interrupted();
}
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) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 没有后续节点或后续节点为CANCELLED
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点开始向头节点遍历,遍历到整个队列最前排的waitStatus<=0的节点,赋值给s,用于后续的unpark操作。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒后续节点执行自旋操作
LockSupport.unpark(s.thread);
}
释放过程简单明了
独占式同步状态的获取和释放总结:
共享式的主要特点在于state的值,在初始化Sync的时候,会去设置AQS中的state值,state是多少就代表可以同时多少个线程获取该锁,比如实例化Semaphore的时候
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
// 设置state的初始值
setState(permits);
}
}
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;
// 退出自旋的节点变成首节点
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();
}
}
与独占式一样,共享式获取也是需要释放同步状态,通过调用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;
}
// 唤醒后续节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
unparkSuccessor方法和独占式是一样的。
共享模式比独占模式多做了一步操作,就是调用了doReleaseShared
方法,去唤醒队列中所有共享模式的节点,让这些线程在去争夺共享资源,而独占式是没有这个操作的。
更多的AQS可以参考下一篇文章:ReentrantLock源码解析