前言
AbstractQueuedSynchronizer(AQS)类其实就是一个普通的工具类,用来控制资源并发访问的工具类,它只关心资源可以被谁获取被谁拥有,以及如何处置无法获取资源的对象,至于是如何获取和释放,则由子类来实现。在java.util.concurrent包中有很多类都实现了AQS,常用的有ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore,其中ReentrantLock实现了AQS中的独占锁,CountDownLatch、Semaphore实现了共享锁,ReentrantReadWriteLock两种都实现了。接下来让我们看看AbstractQueuedSynchronizer内部是如何实现并发控制的吧。
相关定义
AQS相关属性
/**
* 等待队列的头结点
*/
private transient volatile Node head;
/**
* 等待队列的尾结点
*/
private transient volatile Node tail;
/**
* 同步结点状态,该状态用来保证线程同步,相当于锁,线程都会来修改该状态,默认为0
* 如果大于0表示有线程得到了锁,该数为多少就表示同一个线程加锁了多少次,所以可以实现为可重入锁
*/
private volatile int state;
AQS内部类Node
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类使用
static final int CONDITION = -2;
// 节点状态之一,表示状态需要向后传播
static final int PROPAGATE = -3;
// 节点状态,初始值为0,其它值有上面四个
volatile int waitStatus;
// 前一个节点
volatile Node prev;
// 后一个节点
volatile Node next;
// 节点中的线程
volatile Thread thread;
// 等待条件的下一个节点
Node nextWaiter;
}
独占锁
锁的获取
acquire()
public final void acquire(int arg) {
// tryAcquire方法尝试获取锁,由子类实现,所以既可以实现为公平的排队获取,也可怕实现为非公平的抢占式获取
// 如果获取失败(修改state状态),则创建一个节点加入到队列中去,acquireQueued会返回中断状态
// 如果线程中断状态为true,则调用selfInterrupt方法进行中断
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter()
此方法用来将当前线程添加至队列末尾,添加完成后还未将线程挂起
private Node addWaiter(Node mode) {
// 由于不是共享模式,传入进来的mode是null,通过改Node构造方法,会将mode赋值给node中的nextWaiter
Node node = new Node(Thread.currentThread(), mode);
// 通过CAS操作将该节点插入到队列末尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果没插入成功,说明有竞争情况,调用enq进行自旋插入
enq(node);
return node;
}
enq()
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;
}
}
}
}
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;
}
// shouldParkAfterFailedAcquire方法判断是否需要挂起当前线程,如果返回false,则当前线程没有被挂起,进行下一次循环尝试获取锁
// parkAndCheckInterrupt方法用来挂起线程并返回当前线程的中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire()
此方法用来判断是否可以挂起当前线程,并清理队列中已经取消竞争的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 如果前一个节点的waitStatus为SIGNAL时,返回true,表示当前线程可以挂起
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 如果大于0,表示waitStatus为CANCELLED,则遍历队列将所有状态大于0的节点删除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} 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.
*/
// 其它情况则将当前节点的前一个节点waitStatus设置为SIGNAL,此时当前方法会返回false
// 当前线程不会挂起,在循环中再一次尝试获取锁,如果没获取到,再次进入本方法就会进入第一个判断返回true,挂起当前线程
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
获取锁总结
- 调用tryAcquire尝试获取锁(由子类实现),成功则直接返回,失败则进行下一步
- 调用addWaiter方法将当前线程添加到队列末尾,并返回当前线程组成的节点
- 添加到队列末尾之后,调用acquireQueued再次尝试获取锁
- 获取成功,则将当前节点设置为头节点
- 获取失败,则将当前线程在此挂起,下次唤醒还会进入循环中尝试获取锁
释放锁
release()
public final boolean release(int arg) {
// 释放锁成功之后,调用unparkSuccessor()方法唤醒头结点的后继节点
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)
compareAndSetWaitStatus(node, ws, 0);
// 获取头节点的下一个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// 如果下一个节点为null或者状态为取消状态,则从尾结点开始向头节点遍历,直到最后一个(即离头节点最近的)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);
}
本方法唤醒头结点的后继节点之后,线程会在上面的acquireQueued()方法的循环体中醒来,然后在循环中尝试获取锁。
以上所讲的都是AQS中独占锁,下面开始看一下AQS中的共享锁是如何实现的。
共享锁
锁的获取
acquire方法
// 普通的获取共享锁方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/**
* Acquires in shared uninterruptible mode.
* @param arg the acquire argument
*/
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);
// 获取到的r表示还剩多少,如果大于0表示还可以继续竞争锁
if (r >= 0) {
// 将当前节点设置为头节点并唤醒后面的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// shouldParkAfterFailedAcquire方法可以查看上面的解析
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagete
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();
}
}
锁的释放
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
/*
* 这里的代码要结合上面的doAcquireShared方法和里面的shouldParkAfterFailedAcquire方法看
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果头节点的waitStatus == Node.SIGNAL,则表明还有后续节点,则更改头节点状态为0,唤醒后续节点
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 如果头节点waitStatus == 0,则表明没有后续节点,那么将头节点状态设置为PROPAGATE,这样下一个添加进来的后续节点在shouldParkAfterFailedAcquire方法中判断状态不为SIGNAL,会再次尝试获取锁
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}