AQS简介
AQS—AbstractQueuedSynchronizer是JDK工具包中的一个抽象类。在这个抽象类中,有几个属性和一个双向队列(FIFO)。是JUC并发包下的一个基类,那么我们熟知的ReentrantLock、CountDownLatch、信号量等等都是基于这个基类来实现的。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
// 排他锁的标识
static final Node EXCLUSIVE = null;
/** 如果带有一个标识,证明失效了 */
static final int CANCELLED = 1;
/** 具有这个标识,说明后继节点需要被唤醒 */
static final int SIGNAL = -1;
// Node对象存储标识的地方
volatile int waitStatus;
// 上一个节点是谁
volatile Node prev;
// 下一个节点是谁
volatile Node next;
// 当前Node绑定的线程
volatile Thread thread;
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
从上述代码中我们可以看到,AbstractQueuedSynchronizer中有一个head字段和tail字段,head和tail都指向一个Node对象,这个Node对象又维护一个prev和next字段,分别又指向了一个Node,这样就形成了一个双向链表的结构。
ReentrantLock
首先我们先来看下ReentrantLock的源码。
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
...
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public void unlock() {
sync.release(1);
}
...
}
在ReentrantLock中,持有一个Sync对象,这个Sync是一个内部抽象类,大家可以看到这个内部类继承了AbstractQueuedSynchronizer类。一些核心的方法,都是由这个内部类来完成的。下面我们针对加锁释放锁等核心方法来解析。
加锁
Sync有两个实现,一个是公平锁,一个是非公平锁。我们先看非公平锁。我们跟踪代码到Sync的NonfairSync实现类
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
根据上面的代码,可以看到,lock方法是先进行了一次CAS的操作,尝试把state属性从0改成1。如果成功就把当前线程赋值给父类的一个属性。如果没有成功就进入了acquire方法
acquire方法
这是加锁的核心
public final void acquire(int arg) {
// 先再次尝试获取锁资源
if (!tryAcquire(arg) &&
// 尝试失败,就将当前线程封装成一个Node,追加到AQS的队列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 线程中断
selfInterrupt();
}
tryAcquire方法
通过追踪代码,我们最终还是追踪到了ReentrantLock中的一个方法(非公平),如下所示
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取现在AQS的值
int c = getState();
// 如果值为0,表示锁资源已经被释放了,尝试再次获取锁资源
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 尝试成功,并设置当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 不是0 检查当前占有锁资源的线程是否是当前线程,这里为了检查重入
else if (current == getExclusiveOwnerThread()) {
// 当前的state值+1
int nextc = c + acquires;
// 超过了int能表示的正数最大值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 重新对state赋值
setState(nextc);
// 锁重入成功
return true;
}
return false;
}
注释上已经写的比较明确了,首先获取当前的state值,如果为0表示锁已经被释放掉了,那么就先尝试去获取锁。如果不为0,就要判定是否为重入锁,判断标准就是占有锁的线程是否是当前线程,如果是当前线程state就+1,如果不是就返回false表示加锁失败。
addWaiter方法
我们从acquire方法中还有两个方法没有追踪,一个是addWaiter,一个是acquireQueued。我们先看addWaiter方法(AQS提供的方法),这个方法主要是为了封装一个Node对象出来。
private Node addWaiter(Node mode) {
// 创建一个node对象
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 获取尾部节点
Node pred = tail;
// 如果不是空队列
if (pred != null) {
// 新node的前置设置成尾部节点
node.prev = pred;
// 尝试把尾结点修改成当前node节点
if (compareAndSetTail(pred, node)) {
// 老的尾节点的后置节点指向新node节点
pred.next = node;
return node;
}
}
enq(node);
return node;
}
所以这里我们看到,新创建的Node往尾部去插入。可以看成是链表的插入方法。如果当前队列是空的,或者修改尾结点失败,就会进入enq方法
再看enq方法
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
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方法,我们可以简单的理解成,就是进入一个死循环,循环内部还是继续尝试替换尾结点。直到替换成功
acquireQueued方法
上一步已经把Node放入了双向队列中。那么还差一步,就是调用。首先定义了两个boolean类型的标志,一个是为了判断执行是否成功,另一个判断是否有中断。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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主要就是把当前线程和上一个节点
// 都清空
setHead(node);
// 原来的头结点去掉next指针,方便gc
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
我们又看到了一个死循环,那么这个循环内部就是反复的尝试获取锁资源。
1、先获取当前节点的prev节点;
2、如果prev节点是头结点,就尝试获取锁;
3、如果获取锁成功了,把自己设置成头节点,把老的头结点的指针清空,并成功返回;
4、如果prev节点不是头结点,或者尝试获取锁失败了,就进入下一个判定shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt(),我们再看跟进看下这两个方法。
shouldParkAfterFailedAcquire
从方法的注释可以了解这段代码的意思。“检查并更新获取锁失败的node,如果线程应该阻塞,就返回true”。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取上一个节点的状态 1失效了 -1正常
int ws = pred.waitStatus;
// 如果上一个节点状态为-1正常那么我就可以安全的等待了
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 如果上一个节点状态为1 失效了,就继续往上找,找到状态小于0,然后连上
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
}
// 上一个节点不是-1,也不大于0,那么要么是0或者是-3
// 那么将上一个有效节点状态修改为-1,因为只有-1才能正常唤醒下一个节点
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
上述代码主要分成几个步骤。
1、获取当前节点的上一个节点的状态 => ws
2、检查ws,如果是-1,跳到3,如果是1跳到4,如果是其他跳到5;
3、如果前一个节点的状态是-1表示正常,那么直接返回true;
4、如果前一个节点的状态是1,表示是无效节点,那么就依次继续向上找,直到找到节点的状态是-1的,并重新建立连接,丢弃中间状态不是-1的节点;
5、尝试把前一个节点的状态修改为-1;
parkAndCheckInterrupt方法
线程挂起。基于UNSAFE.park(false, 0L)的方法来挂起线程
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
相信大家都晕了吧,当然也有可能根本都还没有看到这里,能够坚持看到这里的,给你一个666,现在,我们尝试画一个流程图来总结一下上述的代码。
释放锁
加锁讲完了,我们再来看看释放锁。如果看明白了加锁的操作,释放锁的操作相比就简单多了。我们先看释放锁的代码。
public void unlock() {
sync.release(1);
}
调用了AQS的release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(arg)方法是个抽象方法,我们再看ReentrantLock中的实现
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 如果所在线程不是AQS的中持有的锁的线程 抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果state已经是0了,表示锁完全退出,释放线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 更新state值
setState(c);
return free;
}
上面这段代码,如果看完了加锁的过程的,就比较容易理解了。
1、获取当前的state值(getState()),减去releases的值,这里是1,也是当前state值减一得到一个值c;
2、如果所在线程不是AQS的中持有的锁的线程,就抛出异常;
3、如果state已经是0了,表示锁完全退出,释放线程;
4、不管state是不是0,都更新state值。
释放了锁持有之后就进入了如下方法中:
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
第一步:获取头节点
第二步:unparkSuccessor(Node head),其他的节点在没有获取锁的情况下就park了嘛对不对,现在要唤醒就是unpark;
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.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);
}
1、获取head节点的waitStatus的值ws;
2、如果ws小于0,就先把头结点的waitStatus通过CAS改为0;
3、先找头节点的next节点,如果next为空,或者waitStatus大于0都表示不可用,此时要注意,是从tail节点开始循环,从后往前找waitStatus <0 的node,这里看到代码中并不是找到就break,而是一直找到头结点,然后找距离头结点最近的那个waitStatus <0 的node。
4、把这个节点的线程从挂起状态中释放
注:这里为什么是从尾节点开始找呢?笔者认为主要是加锁的时候acquireQueued的这段代码
} finally {
if (failed)
cancelAcquire(node);
}
cancelAcquire方法就不贴出来了,但是里面有一个关键的代码就是
// 状态改为取消
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
}
此时会把next节点设置成null。所以如果从头往后找,会发现后置节点为null的情况。