前言
AQS(AbstractQueuedSynchronizer) 同步器 JDK1.5 提供了 java .util.concurrent 包(JUC)大大的提升了并发性能,而AQS是JUC的核心。 他是用来构建锁(比如ReentrantLock)和其他同步工具(比如CountDownLatch等)的基础框架。AQS本身是一个抽象类,它定义了获得锁和释放锁的代码结构,所以如果要新建锁,只要继承 AQS,并实现相应方法即可。
AQS 的构成
AbstractQueuedSynchronizer继承 AbstractOwnableSynchronizer,是为了知道当前是哪个线程获得了锁。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node{...}
//同步队列head
private transient volatile Node head;
//同步队列tail
private transient volatile Node tail;
//状态值,根据状态判断是否可以获得锁
private volatile int state;
//条件队列
public class ConditionObject implements Condition, java.io.Serializable {...}
state 属性
AbstractQueuedSynchronizer定义了一个volatile的int类型的成员变量state来表示同步状态,通过CAS完成对state值的修改。 比如在CountDownLatch中,state就是构造函数中传入的倒数数量,Semaphore中,state就是剩余的许可证数量。ReentrantLock中,表示锁重入计数。
同步队列
FIFO 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;
//节点在从同步队列转移到条件队列中,节点线程等待唤醒
static final int CONDITION = -2;
//当前线程处在SHARED,该状态的线程处于可运行状态
static final int PROPAGATE = -3;
// 表示当前节点的状态,通过节点的状态来控制节点的行为
// 普通同步节点,就是 0 ,条件节点是 CONDITION -2
volatile int waitStatus;
//当前节点的前节点
volatile Node prev;
// 当前节点的下一个节点
volatile Node next;
// 当前节点的线程
volatile Thread thread;
Node nextWaiter;
AbstractQueuedSynchronizer定义了两种资源共享方式:SHARED(共享)和EXCLUSIVE(独占)。同步队列是一个双向队列,当多个线程都来请求锁时,排它锁模式下。某一时刻有且仅有一个线程能够获得锁,那么剩余获取不到锁的线程,就会阻塞,到同步队列中去排队,AQS内部维护了一个CLH队列来管理锁,线程尝试获取锁,如果失败,就将当前线程以及等待状态等信息会包装成一个Node节点,加入到同步队列Sync Queue尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点。
如何获取锁
acquire/acquireShared
AbstractQueuedSynchronizer采用模板方法模式实现acquire()来获取锁资源。
- acquire()独占锁的实现
public final void acquire(int arg) {
if (!tryAcquire(arg) && //尝试执行一次 tryAcquire,如果成功直接返回,失败执行acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- acquireShared()共享锁
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- 首先,tryAcquire()、tryAcquireShared(),AQS并没有实现该方法,交由子类来自己实现
以下已独占模式举例分析源码
- 如果执行tryAcquire()失败,则调用addWaiter将线程加入同步队列,把当前线程放到同步队列的队尾;
private Node addWaiter(Node mode) {
//将当前线程包装成Node, mode 表示 Node 的模式(独占/共享)
Node node = new Node(Thread.currentThread(), mode);
// CAS 将node放入队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- 入队后调用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)) {
//获取锁,设置成head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//把 node 的前一个节点状态置为 SIGNAL
if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt 阻塞当前线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 如果获得node的锁失败,将 node 从队列中移除
cancelAcquire(node);
}
}
// pred 是前一个节点,node 是当前节点。 把前一个节点状态置为 SIGNAL。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 如果前一个节点 waitStatus 状态已经是 SIGNAL 了,直接返回
if (ws == Node.SIGNAL)
return true;
// 如果当前节点状态已经被取消了。
if (ws > 0) {
// 找到前一个状态不是取消的节点,因为把当前 node 挂在有效节点身上
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 否则直接把节点状态置 为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
如何释放锁
独占锁模式,从队头开始,找它的下一个节点,如果下一个节点是空的,就会从尾开始,一直找到状态不是取消的节点,然后释放该节点。
// unlock 的基础方法
public final boolean release(int arg) {
// tryRelease 交给实现类去实现,一般就是用当前同步器状态减去 arg,如果返回 true 说明成功释放锁。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 从头开始唤醒等待锁的节点
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
//头结点的waitStatus
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
if (s == null || s.waitStatus > 0) {
s = null;
// 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果当前节点的下个节点不为空,状态<=0,就唤醒当前节点
if (s != null)
LockSupport.unpark(s.thread);
}
CountDownLatch中AQS的实现
CountDownLatch是一种灵活的闭锁实现,它可以使一个或者多个线程等待一组事件发生。闭锁包括一个计数器,该计数器初始化时是一个正数,表示等待的事件数量,countDown方法递减计数器,表示一个事件发生了,而await方法等待计数器达到零,表示所有需要等待的事件都已发生。如果非零,那么await方法会一直阻塞到计数器为零,或者等待线程中断或超时。
源码分析
可以看出,CountDownLatch实现了AQS的tryAcquireShared和tryReleaseShared方法,是以共享方式获取和释放资源的。
- 构造函数 创建CountDownLatch时候传入的倒计数count就是AQS的state值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- await() CountDownLatch的await方法,是调用AQS的doAcquireSharedInterruptibly获取锁
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- countDown() 调用releaseShared(1);
CountDownLatch将任务分为count个线程与执行,将state初始化等于count,线程并行执行,每完成一个后countDown()一次调用tryReleaseShared,将state通过CAS算法减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。