AQS是AbstractQueuedSynchronizer的简称,juc包下锁的实现,基本上需要借助于AQS的功能,如下图所示:
通过继承结构,可以看到,常用的可重入锁ReentrantLock,以及同步辅助工具类CountDownLatch、Semaphore,都用到了AQS。
首先要明白:锁分为独占锁和共享锁,独占锁又分为公平锁和非公平锁。
- 独占锁:所谓独占锁,就是某一时刻,该锁只能被一个线程占有的
- 共享锁:共享锁则是某一时刻,该锁可以被多个线程同时占有
- 公平锁:获取锁的顺序,和请求锁的顺序是一致的
- 非公平锁:获取锁的顺序,和请求锁的顺序不一致,存在抢占机制。
下面将通过ReentrantLock中的代码,以公平锁为例,聊聊AQS获取锁的过程。先看ReentrantLock类的结构,如下图所示:
可以发现,ReentrantLock中,有3个内部类:Sync、FairSync(公平锁的实现)和NonfairSync(非公平锁的实现),并且FairSync和NonfairSync继承于Sync,而Sync继承了AQS:
以FairSync实现为例,我们加锁一般是通过lock()方法,该方法也是获取锁的顶级入口,看其实现:
final void lock() {
acquire(1);
}
可以看到,它调用了acquire()方法:
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
根据注释可知:
- acquire()方法,获取的是独占锁,并且忽略中断
- tryAcquire()方法,尝试获取锁,至少会执行一次
- 如果tryAcquire()方法尝试获取锁失败,则通过addWaiter()方法,将当前线程放入同步队列
- acquireQueued()方法,可能会重复经历阻塞、唤醒、尝试获取锁的过程,直到成功获取锁
接下来,主要是分析这3个方法:
- tryAcquire(int arg)
- addWaiter(Node node)
- acquireQueued(addWaiter(final Node node, int arg)
1. 尝试获取锁:tryAcquire(int arg)
protected final boolean tryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 同步锁状态
int c = getState();
// 如果状态为0,则表示没有线程持有该锁
if (c == 0) {
// 队列的前面没有等待锁的线程,通过CAS设置锁的状态为acquires,也就是1
// 并且标记当前线程是占有该锁的线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果占有锁的线程是当前线程
else if (current == getExclusiveOwnerThread()) {
// next是锁将要更新的状态,可以看到,值是累加的,这也就说明了该锁是可重入锁
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 设置锁的状态
setState(nextc);
return true;
}
return false;
}
注释在代码中了,其流程大概如下:
- 如果同步锁的状态state为0,则并且队列种没有比当前线程等待更久的线程,则尝试获取锁
- 如果当前线程是占有该锁的线程,不需要重新获取锁,只需要更新锁的状态(累加1),实现了可重入
2. 将线程添加到同步队列:addWaiter(Node node)
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
// 以当前线程和给定的模式(独占式)创建节点
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 如果尾节点不为空(也就是链表不为空),则将当前节点插入到链表的末尾,成为新的尾节点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 通过enq()方法,添加到链表的末尾
enq(node);
return node;
}
通过注释以及代码,可以知道:
- addWaiter()方法的作用就是将当前线程包装成节点,并添加到链表的末尾
- 添加到链表的末尾的两条路径:(1)如果链表不为空,则通过尾节点链接 (2)如果链表为空则通过enq()方法添加
看下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;
}
}
}
}
可以看到,enq()方法通过死循环,也就是自旋的方式来设置的
3. 获取锁: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);
}
}
看下它是如何判断当前线程是否需要阻塞的:
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.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 向前找,直到找到一个waitStatus不是cancelled的节点,作为当前节点的前一个节点
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的值为0(初始状态)或者是PROPAGATE
// 设置waitStatus的值为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
可以看出,只有当前一个节点的状态为SIGNAL时,才会阻塞当前节点所包装的线程。
总结一下获取锁的过程:
- 通过tryAcquire(int arg),尝试获取锁,只尝试了一次,所以有可能没有获取成功
- 如果tryAcquire(int arg)没有获取成功,则通过 addWaiter(Node node)将线程封装成Node节点,链接到链表的末尾
- 通过acquireQueued(addWaiter(final Node node, int arg)的死循环,不断尝试获取锁,直到成功