经过上一篇文章AQS(AbstractQueuedSynchronizer)源代码分析(一)——实现逻辑概览,可以大致的了解AQS的一个运行机制。本次将会具体看一下AQS的代码实现。首先说明一下,AQS的aquired和release方法大致分为两类:
1、非共享的,如:aquire,tryaquired,release, tryrelease等;
2、共享的,如:acquireShared,tryAcquireShared,releaseShared,tryReleaseShared等。
本次我们将重点看非共享的实现,至于共享的实现,我们在下一篇文章再讨论。
AQS实现逻辑概览图:
关于这个图的解释可以去上一篇文章(实现逻辑概览)中了解,下文统称为“概览图”。
一、acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
selfInterrupt();
}
}
上面是acquire方法的代码实现。方法说明:
方法体中使用了一个快速与(&&)。首先执行tryAquire(arg)方法,这个方法在AQS中默认是抛出异常的,所以其具体的执行是由AQS的子类实现的。因此我们需要找一个子类看一下tryAquire(arg)方法的实现。我们这里采用的类是:ReentrantLock----->Sync(ReentrantLock的内部抽象类)----->NonfairSync(Sync的子类)。NonfairSync的tryAquire(arg)方法的实现如下:
//ReentrantLock.NonfairSync类
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //获取当前的sate值,getSate方法是AQS中的方法
if (c == 0) { //判断sate值是否为0,(在这里0就表示初始状态,在其他类的实现中,sate的初始值可能是别的值,不要认为初始值一定是0);
if (compareAndSetState(0, acquires)) { //采用CAS原子操作修改sate的值,
//如果修改成功,则将AQS的执行者设置为currentThread;这里的执行者其实就是获得执行权的线程
//这个属性主要是用来确定当前正在执行的线程的
setExclusiveOwnerThread(current);
return true;//如果修改成功,返回true
}
}
else if (current == getExclusiveOwnerThread()) {
//如果sate状态不为初始值0,说明已经有线程正在执行。
//此时就需要做一个区分,正在执行的那个线程与当前进入的线程是否是同一个线程
//如果是同一个线程,比如:递归调用或者重入等等,则任然可以继续执行,但是应该记录重入的次数。
//如果不是同一个线程,那么就应该挂起这个线程,返回false
int nextc = c + acquires; //这里就是用来记录重入的次数的,每次重入都需要修改sate
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;//如果修改失败,返回false
}
上面的代码说明,已经解释了tryAquire(arg)方法的实现逻辑(仅仅是其中一种)。这里再回到AQS中的acqured方法。在acquired方法中:
如果tryAquire(arg)成功,返回true; 则acquire方法就立即返回了。概览图标示:尝试修改sate---->(是)------>执行被锁定的内容。
如果tryAquire(arg)失败,返回false; 则快速与(&&)就会执行acquireQueued方法。概览图标示:尝试修改sate---->(否)------>增加node节点,挂起线程。
根据上面的描述,在acquireQueued方法中需要完成两件事:第一,增加node节点;第二,挂起线程,使线程处于WAITING状态,等待被唤醒。acquireQueued方法的代码如下:
//在执行acquireQueued之前,先执行了addWaiter(Node.EXCLUSIVE)方法获得了第一个参数。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
先看一下addWaiter方法的实现:
/**
* 在node队列的队尾追加一个新节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //使用Node.EXCLUSIVE和currentThread创建一个节点
// Try the fast path of enq; backup to full enq on failure
//node队列的第一个节点被称为:head; 最后一个节点被称为;tail
Node pred = tail;
if (pred != null) {
node.prev = pred; //node.prex = tail, tail.next = node; 调换引用,切换队列的末尾节点。
//因为是多线程,所以会有多个线程同时追加node,所以用CAS修改尾节点,如果成功,则返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//CAS修改tail可能会失败,所以需要在enq方法中处理
return node;
}
private Node enq(final Node node) {
for (;;) { //死循环,保证一定可以追加成功
Node t = tail;
//当AQS第一次被访问时,还没有tail和head节点,所以是null; 此时就需要新增一个空节点来保证队列的初始化
if (t == null) {
if (compareAndSetHead(new Node())) //CAS修改,如果失败,死循环会保证继续尝试
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { //CAS修改,如果失败,死循环会保证继续尝试
t.next = node;
return t;
}
}
}
}
上面对addWaiter方法的分析,已经完成了第一个功能:node节点的增加;接下来看一下如何挂起线程。这部分不容易理解,先用图说明一下:
如上图所示,head表示正在执行的线程。当head执行完成之后,会调用release方法唤起head的nextNode节点线程。线程被唤醒,之后就会再次去tryqcquired;如果tryqcquired成功,则就会将head替换成node节点,然后执行node。接着看一下acquireQueued的代码:
/**
* 追加节点之后,循环尝试修改sate
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //死循环保证这个过程将会一直持续,直到return语句执行
final Node p = node.predecessor(); //获得node的prev(前一个节点)
//如果前一个节点是head,则进行tryacquired
//如果正在执行的head节点没有调用release方法,这里的tryacquired将永远是false,不可能成功;所以就会执行下面的挂起方法
if (p == head && tryAcquire(arg)) {
setHead(node); //替换head节点,准备执行
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire这个方法是用来判断线程是否可以挂起的,下文单独说明
//因为没有tryacquired成功,所以会执行parkAndCheckInterrupt挂起线程,通过park方法
//当再次被唤醒时,会继续上面的操作
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //挂起线程,类似object.wait方法;挂起之后将会等待被唤醒,以便于执行上面的tryAcquire
return Thread.interrupted();
}
接下来是shouldParkAfterFailedAcquire是用来判断线程是否能够挂起的。这里涉及了node节点的几个状态:
1、SIGNAL,这个值小于0 , 表明该node的存在nextNode后续节点,相当于告诉node:有nextNode需要处理,你处理完之后,去唤醒nextNode。
2、CANCELLED,这个值是大于0的,表明这个节点的线程已经取消了,不需要再执行
3、CONDITION,表明node在执行被锁定内容之前,需要等待某个条件。这个会在后面的文章讨论。这里不做分析
shouldParkAfterFailedAcquire的代码:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //获取node的状态
if (ws == Node.SIGNAL)
return true; //已经是SIGNAL,可以挂起,返回true
if (ws > 0) { //节点已经被取消,需要从队列当中移除
do {
node.prev = pred = pred.prev; //调换prev引用,移除节点
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果没有取消,也不是SIGNAL状态,则尝试修改为SIGNAL,
//node是prevNode的nextNode,说明prevNode存在后续节点,也就表明prevNode需要是SIGNAL状态,在prevNode执行完之后,
//需要去唤醒nextNode。因此,如果prevNode不是SIGNAL状态的话,需要修改为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false; //返回false,线程不能挂起
}
二、release方法
上面已经介绍完acquire方法,接下来看一下release方法:
public final boolean release(int arg) {
if (tryRelease(arg)) { //尝试还原sate,如果成功则执行if内语句
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus; //这里的node就是head节点
if (ws < 0) //head节点是正在执行的node,其状态不可能是小于0,也就是不能是SIGNAL
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; //取出head节点的nextNode节点
if (s == null || s.waitStatus > 0) { //判断nextNode是否为Null,或者是否已经取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) //如果已经取消,则循环取出最近的一个状态为SIGNAL的节点
if (t.waitStatus <= 0) //waitStatus<0,node是SIGNAL状态。新建的,未经过挂起和其他处理的节点,状态是0。
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //通过unpark唤起node中的线程
}
}
上文采用ReentrantLock.NonfairSync说明了tryacquired方法。这里也采用ReentrantLock.NonfairSync类中的tryRelease方法说明一下tryRelease的实现。不过ReentrantLock.NonfairSync的tryRelease方法在其父类Sync中,代码如下:
类ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //在tryacquired方法中时增加sate, 而这里是减sate
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); //释放AQS的执行权
}
//修改sate状态值,这里没有CAS操作,
//因为上面的acquire方法已经挂起了所有后续线程,所以当前修改sate的线程只有一个,不会有并发修改问题
setState(c);
return free;
}
总结:AQS总的来说就是一个操作node队列和控制sate状态的过程,以此来实现多个线程的同步。AQS优秀的地方在于,它将所有对队列的操作封装到了自身,向开发者隐藏了队列操作的复杂性。同时AQS又将过sate的修改和控制暴露给了子类,让子类自定义个这个sate的控制。这样做很好的封装了复杂性,同时又保证了足够的扩展性。