AQS(AbustactQueuedSynchronizer)队列同步器是Java同步的基础组件,ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier,Semaphore等都是基于AQS来实现的,了解AQS的源码对于多线程编程还是有一些帮助,也可以实现自定义的同步组件。
AQS使用了模板方法设计模式,提供了独占锁/共享锁获取以及释放等大概8个模板方法,为用户空出来几个供重写来实现自定义同步逻辑的方法,此外AQS还有一个Condition内部类,提供了类似于wait/notify的等待/通知机制。整体上是通过一个FIFO非阻塞同步队列和CAS操作实现非阻塞的锁获取,释放,线程的阻塞唤醒则是通过Unsafe库的park,unpark实现。源码中大量使用了CAS操作,如果不了解可以先去了解一下。
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
/**
* 1.超时时间小于等于spinForTimeoutThreshold就会采用自旋
* 2.超时时间大于spinForTimeoutThreshold会让线程阻塞
*/
static final long spinForTimeoutThreshold = 1000L;
static final Node SHARED = new Node(); //共享模式
static final Node EXCLUSIVE = null; //独占模式
static final int CANCELLED = 1; //表示线程已经被中断或者等待超时,需要从同步队列中删除
static final int SIGNAL = -1; //表示后继线程处于等待状态,当前节点释放锁之后会及时通知后继节点
static final int CONDITION = -2; //表示当前节点等待在某个Condition上
static final int PROPAGATE = -3; //用在共享模式下,表示下次同步状态会无条件传播下去
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter; //节点是否共享/独占 和 等待对列中的下一个节点共用这一字段
final boolean isShared() { return nextWaiter == SHARED; } //判断是否共享节点,就是根据nextWaiter
下面着重分析一下AQS的几个模板方法
public final void acquire(int arg) //获取独占锁
public final void acquireInterruptibly(int arg) //获取独占锁,可响应中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout) //获取独占锁,超时返回,可响应中断
public final void acquireShared(int arg) //获取共享锁
public final void acquireSharedInterruptibly(int arg) //获取共享锁,可响应中断
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) //获取共享锁,超时返回,可响应中断
public final boolean release(int arg) //释放独占锁
public final void releaseShared(int arg) //释放共享锁
public final void acquire(int arg) {
/**
* 1. 根据if短路,当获取锁成功后就不执行后续代码
* 2. 获取锁失败,先通过addWaiter方法将创建一个独占线程节点,加入阻塞队列
* 3. 然后让阻塞队列中的所有线程都自旋方式来获取锁
* 4. 如果获取锁失败,并且加入队列失败,或者超时等,会进入if体,中断自己
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire函数是空出来留给用户实现具体的获取逻辑,需要线程安全的获取同步状态,获取失败之后,先调用addWaiter方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //新建一个独占/共享模式的线程节点
//进行一次快速insert操作,如果成功就返回
//否则就进入enq方法,死循环的CAS尝试将自己加入尾结点
Node pred = tail;
if (pred != null) {
/**
* 首先将自己连接到链表的tail上
* 然后CAS替换tail,如果此时成功,就说明上一句没有受到并发影响,乐观机制
* 最后把原来的tail的next指向自己,因为自己现在是tail
*/
node.prev = pred;
if (compareAndSetTail(pred, node)) {
/**
* 只要CAS操作通过,下面这条语句就不会出现并发问题
* 因为每个线程的node都是不同的,只要保证pred是当前的node的前驱就可以
* 而CAS已经经过校验保证了这一点
*/
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 如果没有tail节点,说明队列为空,随便加一个
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { //尝试CAS设置尾结点为node
t.next = node;
return t;
}
}
}
}
addWaiter方法通过CAS将并发添加变的串行化,添加完成之后,调用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;
}
/**
* 非头结点一直检测自己的前驱状态,如果发现自己的前驱节点有变化
* 就立刻进行下一次获取锁的尝试,否则通过lockSupport来继续park
* park(阻塞)是通过unsafe包来实现的,native方法
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; //在park中出现中断,会执行到此,但是不会响应中断
}
} finally {
if (failed) //线程中断,需要取消获取锁
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire函数和名字描述一样,就是判断一个结点应该park(阻塞)还是自旋在一次失败获取锁之后。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/**
* 位于SINGAL状态,如果他被取消或者释放锁,会通知后续节点
* 也就是这里的node,所以node可以安心park
*/
return true;
if (ws > 0) {
/**
* 前驱节点已经被中断或者等待超时
* 所以node节点需要把这些前驱节点移除阻塞队列
* 返回false,不需要park
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/**
* 节点状态肯定是0或者PROPAGATE,这时候需要让节点在进行一次尝试获取锁,不应该park
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
在doAcquireInterruptibly函数检测到线程中断的时候,直接抛出异常,其他前面的逻辑都是一样的
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); //会抛出中断异常,响应中断
}
} finally {
if (failed)
cancelAcquire(node);
}
}
* 1. 成功获取到锁,短路
* 2. 获取锁失败,尝试等待nanosTimeout这么久,超时返回
* 响应中断和超时,其他逻辑和acquire几乎相同
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
doAcquireNanos函数中对超时事件做出了响应
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L) //检测是否超时
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);//超过自旋阈值,进行阻塞
if (Thread.interrupted())
throw new InterruptedException(); //响应中断
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) { //如果尝试释放锁成功
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒头结点的后继节点
return true;
}
return false;
}
unparkSuccessor唤醒后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) //CAS修改头节点状态
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) { //寻找下一个符合条件的结点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)//从tail开始倒着找,直到node停止
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒找到的符合条件的节点
}
/**
* tryAcquireShared留给用户重写获取共享锁方法
* 返回值负数表示获取失败
* 0表示获取成功,但是后续节点无法获取成功
* 正数表示获取成功且后续节点也可能获取成功
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
doAcquireShared函数
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);//将head设置为node,p是node的前驱
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate函数很关键,在这里对共享锁进行扩散
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/**
* 1.tryAcquire返回值是正数,表示后续节点仍然可以获取共享锁,唤醒后续节点
* 2.头结点为空或者状态是负,也唤醒后续节点
* 这里采用了保守的策略,虽然可能唤醒导致不必要的唤醒,但是大多数节点很快都会被唤醒
* 所以影响不大
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
/**
* 如果后继节点是共享的,或者是出现了空,都会执行唤醒的操作
*/
if (s == null || s.isShared())
doReleaseShared(); //唤醒节点
}
}
doReleaseShared函数在这里具体的节点状态检测和唤醒
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { //如果节点的状态是SINGAL,且CAS成功就唤醒该节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); //前面已经有提到了
}
//如果节点的状态是0,尝试CAS设置为PROPAGTE,以便下次共享锁传播
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果其他线程修改了头结点,那么在执行一次唤醒的逻辑
if (h == head) // loop if head changed
break;
}
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared(); //调用的doReleaseShared函数在共享锁获取的时候,已经解释过,他们调用的都是同一个函数
return true;
}
return false;
}
AQS的核心代码其实就是acquire和release函数,分别有独占模式和共享模式两种,模板方法都是final的,不可被重写,而且调用逻辑已经被写好了,我们开发自定义同步组件只需要重写tryAcquire和tryRelease方法等即可。