目录
addWaiter【为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部】
acquireQueued【以独占并且非线程中断模式获取已经存在于队列中的线程】
AQS源码大致分为独占(acquire【获取独占】、release【释放独占】)和共享(acquireShared【获取共享】、releaseShared【释放共享】)两种模式,而其AQS模板方法会调用tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively的模板方法,并且都会抛出UnsupportedOperationException(不支持的操作异常)那么就说明不能直接使用AQS来处理业务,而是需要在子类中实现该5个方法才能使用。而Node子类中的方法都比较简单就不做分析了,但是ConditionObject中的等待(await*)和唤醒(signal*、signalAll)相关的方法,也是核心需要专门进行分析,AQS的虚拟双向队列与多个ConditionObject条件队列的关系。
AQS核心公用方法
enq【将节点加入队列(尾部)】
/**
* 将一个节点插入CLH双向队列,必要时初始化
* node.prev = t; 加上 t.next = node; 那么CLH增加了当前节点,并且又成环了
* @param node the node to insert
* @return 返回节点的上一个节点
*/
private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
for (;;) { // 自旋
AbstractQueuedSynchronizer.Node t = tail;
if (t == null) { // 尾部为null,说明还没有初始化过,则CAS初始化,只有一个节点head = tail
if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
tail = head;
} else {
// t是tail,当前节点的前缀链接指向CLH的尾节点
node.prev = t;
// CAS设置到尾部,并将当前节点添加到尾部
if (compareAndSetTail(t, node)) {
t.next = node; // 将原来的CLH的最后一个节点的tail执向当前节点
return t;
}
}
}
}
addWaiter【为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部】
/**
* 为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部
* 整个方法与enq差不多, 只是添加的节点设置了 nextWaiter属性
*
* @param mode Node.EXCLUSIVE 独占模式, Node.SHARED 共享模式
* @return 返回一个新的节点
*/
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
// 为当前线程和参数传入的模式 创建节点
AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), mode);
// 先加入到尾部,但是可能没有初始化(则 tail为null)
AbstractQueuedSynchronizer.Node pred = tail;
if (pred != null) {
// 将当前节点的前置链接设置为原来的尾部
node.prev = pred;
// CAS将尾部由原来替换为现在节点
if (compareAndSetTail(pred, node)) {
// 把原来的尾部节点的 next链接指向当前节点
pred.next = node;
return node;
}
}
// 说明AQS队列可能还没初始化
enq(node);
return node;
}
acquireQueued【以独占并且非线程中断模式获取已经存在于队列中的线程】
/**
* 以独占并且非线程中断模式获取已经存在于队列中的线程
* 该方法用于Condition的等待方法获取锁
*
* @param node 独占节点
* @param arg the acquire argument 子系统自己定义的state值
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
boolean failed = true; // 获取默认失败
try {
boolean interrupted = false;
for (;;) {
// 当前节点的前置节点设置为临时变量 p
final AbstractQueuedSynchronizer.Node p = node.predecessor();
// 如果前一个节点就是CLH的第一个节点了,则意味着该节点可以进行尝试抢占资源
if (p == head && tryAcquire(arg)) {
// 将自己设置为CLH第一个节点,则其他属性置位空。由于这里是独占模式,那么只有该节点是唯一持有资源state的线程
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这里说明还有多个节点,那么就要将上一个节点状态设置为 signal(下一个节点挂起,如果到该节点则需要唤醒下一个节点),
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)
//如果前置节点的状态已经是signal,意味着已经经历过二次尝试,可以返回true,让线程进入等待状态。
return true;
if (ws > 0) {
//如果前置节点处于取消状态,则不断的向前回溯,直到找到一个不是取消状态的节点。无论如何,至少head节点不会是取消状态,所以最终一定可以找到一个不是取消状态的前置节点。然后将该node的pre指针指向该非取消状态节点。在这个循环中就将AQS中的内部队列的长度缩短了。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将前置节点的状态变更为signal。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回false,提示调用者线程仍然需要继续尝试,不可以进入休眠状态
return false;
}
//让线程进入等待状态,并且返回线程的中断信息
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
unparkSuccessor【唤醒CLH中的下一个节点】
管程模型中当上一个节点执行完成后肯定要唤醒下一个等待的节点,这里就相当于 synchronized时,monitorexit时需要唤醒下一个等待的线程,执行任务。
/**
* 唤醒CLH队列中的下一个节点(如果存在,自己是最后一个那就没有下一个了),
*/
private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
// 获取当前节点的状态
int ws = node.waitStatus;
// 如果不是取消和无状态
if (ws < 0) // 将当前节点设置为无状态 0
compareAndSetWaitStatus(node, ws, 0);
// 获取当前节点的后一个节点
AbstractQueuedSynchronizer.Node s = node.next;
// 如果下一个节点为null, 或者下一个节点的被取消了(waitStatus > 0只能是被取消了CANCELLED=1)
if (s == null || s.waitStatus > 0) {
s = null; // 将节点置位空
// 从CLH的尾部进行遍历,因为从前找的链路已经断了
for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) // 非CANCELLED的节点
s = t; // 就唤醒该节点
}
// 再判断,如果后一个节点不为null,则直接唤醒下一个节点的线程 LockSupport.unpark => UNSAFE.unpark
if (s != null)
LockSupport.unpark(s.thread);
}
doReleaseShared【共享模式唤醒CLH中的一个节点或设置传播(唤醒多个)】
/**
* 共享模式的释放锁操作
*/
private void doReleaseShared() {
// 自旋操作
for (;;) {
// 获取CLH第一个节点
AbstractQueuedSynchronizer.Node h = head;
// 第一个节点不是空,并且不是最后一个节点
if (h != null && h != tail) {
int ws = h.waitStatus;
// 获取并判断第一个节点的状态,如果是SIGNAL状态,则要去唤醒
if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
// CAS将当前节点的SIGNAL(要去唤醒别人)修改为无状态
if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
continue; // loop to recheck cases, 失败了再自旋
unparkSuccessor(h); // 上面分析过了unparkSuccessor,唤醒头节点的下一个线程
}
// 将下一个设置为传播行为
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
continue; // loop on failed CAS
}
// 判断一个第一个节点有没有被改变过,改变了就跳出自旋
if (h == head) // loop if head changed
break;
}
}
独占模式
acquire【获取独占锁】
获取独占锁:先调用一次子类实现。再将CLH队列中加入一个独占模式的节点,再调用acquireQueued将线程挂起,把上一个节点的状态设置为SIGNAL,则只有上一个节点能唤醒该节点线程。
/**
* 该方法是排他模式, 忽略中断异常.
* 1、至少会回调一次(因为里面自旋还可能调用)子类实现的tryAcquire方法,返回成功
* 2、接着就会将当前线程排队直到成功
* 否则:不满足1、2 则调用selfInterrupt, Thread.currentThread().interrupt(); 以抛出中断异常
*
* 这个方法可以用于实现 {@link Lock#lock}功能
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
selfInterrupt();
}
release【释放独占锁,唤醒管程的“下一个”节点线程】
/**
* 这个方法用于使用 {@link Lock#unlock}. 即独占模式的释放锁资源,并且唤醒等待锁的下一个线程
* @return 返回的结果就是子类自己实现的 {@link #tryRelease}的结果
*/
public final boolean release(int arg) {
// 调用子类的实现,返回true再执行(即现回调一次子类)
if (tryRelease(arg)) {
// 获取CLH的头节点, 因为当前执行的不一定就是自己
AbstractQueuedSynchronizer.Node h = head;
// 如果头节点部位空,并且状态不是无状态
if (h != null && h.waitStatus != 0)
//上面分析过unparkSuccessor方法,会唤醒head节点的下一个节点或者从后往前找的第一个有效节点 的线程
unparkSuccessor(h);
return true;
}
return false;
}
共享模式
acquireShared【获取共享锁,阻塞剩余资源数的线程】
/**
* 获取共享模式的锁,忽略中断。
* 至少回调一次子类的 {@link #tryAcquireShared} 因为自旋中还会调用可能多次
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/**
* 共享模式获取锁
* @param arg the acquire argument
*/
private void doAcquireShared(int arg) {
// 将共享模式节点和当前线程,添加到CLH的队尾
final AbstractQueuedSynchronizer.Node node = addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (;;) { // 获取当前节点的上一个节点
final AbstractQueuedSynchronizer.Node p = node.predecessor();
// 如果上一个节点是队头
if (p == head) {
// 获取子类实现,因为只有子类知道还能共享的个数(比如限流器设置允许同时5个任务)
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 与独享模式一样, 到这里说明还有多个节点,那么就要将上一个节点状态设置为 signal(下一个节点挂起,如果到该节点则需要唤醒下一个节点),在把下一个节点的线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* Sets head of queue, and checks if successor may be waiting
* in shared mode, if so propagating if either propagate > 0 or
* PROPAGATE status was set.
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
private void setHeadAndPropagate(AbstractQueuedSynchronizer.Node node, int propagate) {
AbstractQueuedSynchronizer.Node h = head; // 记录原来的head节点,下面使用
setHead(node); // 将当前节点设置为头节点
// 唤醒后继结点
//1.共享资源剩余数大于0,有剩余资源肯定是要唤醒后继结点的
//2.头结点不存在。可以看到这个条件会与后文的head!=null相冲突。而且实际上在这个方法执行的时候,head节点是必然存在的,不可能为null。留待下篇文章再做解读。
//3.头结点状态小于0.这里只有两种可能,一种是SIGNAL(-1),一种PROPAGATE(-3)。这两种数值的出现都意味着后继节点要求node(也就是当前head)唤醒后继结点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
AbstractQueuedSynchronizer.Node s = node.next;
// 在AQS的实现类中,存在着所谓读写锁。也就是说在AQS的内部队列,共享节点和独占节点是都存在的。所以共享唤醒传播到独占节点就要停止
if (s == null || s.isShared())
doReleaseShared();
}
}
releaseShared【释放共享锁,唤醒管程的可用节点线程】
共享模式那么可以运行多个线程执行,比如信号量(Semaphore、CountDownLatch)。比较形象的就是限流器(信号量实现的限流器:后续添加链接),只能同时允许5个线程执行,那么当前线程执行完成,则可以允许一个或者多个(有可能当前执行的远小于5个),那么需要唤醒一个或多个线程。
/**
* 释放共享模式的锁,唤醒一个或多个等待该锁的线程
* @return 返回值就是子类自己实现的 {@link #tryReleaseShared}的返回值
*/
public final boolean releaseShared(int arg) {
// 还是回调一次子类自己的实现
if (tryReleaseShared(arg)) {
// 调用公用的释放共享(多个)线程
doReleaseShared();
return true;
}
return false;
}