概括
- AQS是一个队列同步器,实现了多线程获取锁时的排队机制。
- 当多个线程获取锁时,AQS会维护一个队列,按先进先出规则排队。在这个队列中,头节点是正在运行的线程节点,头节点的子节点则是队列唯一可以参与竞争锁的节点,其他节点一般在发现前一个节点不是头节点后直接放弃竞争锁并进入阻塞状态。
- AQS的锁分为独占锁和共享锁
- 独占锁机制下,只有头节点释放锁后才会唤醒后继节点竞争锁,后继节点基本只有一次被唤醒的机会。
- 共享锁机制下,当当前线程获取到锁后,AQS会判断下一个等待线程后没有可能也获取到锁,如果AQS觉得有可能(这里并不是绝对认为它能获取到锁,只是保守觉得有获取到锁的可能性)也能获取到锁,则唤醒后继节点参与获取锁操作。释放共享锁时,当前线程会被判断后继节点是否已经被唤醒,如果已经被唤醒则直接结束,否则再次唤醒后继节点。
- AQS保留了try开头的方法,如tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等等。这些方法是获取锁、释放锁的核心实现,但AQS都交由子类实现,且实现方式决定了锁的性质。为了使读者感觉更形象化,这里举几个例子:
- ReentrentLock(可重入锁):ReentrentLock使用AQS的state字段,该字段是一个整数。当state等于0时表示没有人获取锁,大于0时表示锁被占用。且AQS拥有一个exclusiveOwnerThread字段用于保存当前是哪个线程获取了锁。在ReentrentLock 的tryAcquire 中,只要是state是0或者exclusiveOwnerThread是当前线程则获取锁成功。因此通过这两个简单的判断即可实现可重入锁的性质。
- CountDownLatch(线程同步):CountDownLatch可以使多个线程同步,我们知道假设初始化了同步数量是5,那么调用5次countDown方法后,所有被await阻塞的线程都会通过。CountDownLatch其实有一个内部类继承了AQS类,countDown方法本质就是调用了内部类实现的tryReleaseShared方法,该方法每次把state减1,state的初始值就是初始化CountDownLatch时的同步值,这里是5。当state减了5次1之后,变成0,CountDownLatch就把所有被await阻塞的线程放行。await的实现方式则是调用tryAcquireShared,该方法实现方式也很简单,只要state等于0,就能获取到锁。由于是共享锁,因此state等于0时,所有等待线程都可以获取到锁继续执行。
- ConditionObject 是AQS自带的Condition的实现类,可以实现线程间的交互。
- ConditionObject自身维护了一个阻塞队列,当需要阻塞时会把线程封装成节点加入到阻塞队列中,线程被唤醒时,则把线程移植到AQS队列同步器中等待被唤醒获取锁。
- ConditionObject的await方法会潜在调用release方法,因此应当在上锁情况下调用,且会释放资源,避免带锁阻塞。
AQS属性
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* 队头
*/
private transient volatile Node head;
/**
* 队尾
*/
private transient volatile Node tail;
/**
* 锁状态
* 如等于0即可获取所成功等。
* 如何算获取锁成功交由具体实现类定义。
*/
private volatile int state;
/**
* 获取锁状态
*/
protected final int getState() {
return state;
}
/**
* 设置锁状态
*/
protected final void setState(int newState) {
state = newState;
}
/**
* 通过CAS方式更新锁状态
*/
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
复制代码
核心内部类
Node
Node属性
/**
* 线程节点
* 把线程包装成当前节点,并加入队列管理
* 节点包含了相关管理信息
*/
static final class Node {
/* 下面两个是节点类型 */
/**
* 标记,表示该节点线程申请的是共享锁
* 这个Node好像也没啥用,就单纯用来标记。
*/
static final Node SHARED = new Node();
/**
* 标记,表示该节点线程申请的是独占锁
*/
static final Node EXCLUSIVE = null;
/* 下面4个是锁状态 */
// 表示节点被取消
static final int CANCELLED = 1;
// 表示当前节点的下一个节点正在等待被唤醒。
// 当一个节点获取不到锁即将被阻塞之前,都会把前一个节点设为SIGNAL态
// 如果前一个节点是取消态的话则直接把前一个节点从队列移除,再设置前一个节点
static final int SIGNAL = -1;
// 表示节点正在Condition的阻塞队列中等待
// AQS的同步队列器没有用到该状态。
// 在ConditionObject中用到。
static final int CONDITION = -2;
// 表示后继节点已被执行
// 这个状态只有在共享锁机制下用到。
// 当一个线程唤醒了后继线程之后,该线程会被设为0
// 下一次需要唤醒后继线程时,
// 发现线程状态是0则会换成PROPAGATE态,表示后继线程已经运行了。
// 如果被唤醒的后继线程获取锁失败,在阻塞前会把当前节点重新设为SIGNAL态。
static final int PROPAGATE = -3;
/**
* 用来表示节点状态
*/
volatile int waitStatus;
/**
* 前一个节点
*/
volatile Node prev;
/**
* 后一个节点
*/
volatile Node next;
/**
* 当前节点对应的线程
*/
volatile Thread thread;
/**
* 在AQS中表示保存模式:SHARED 或者 EXCLUSIVE
* 在ConditionObject中表示阻塞队列的下一个节点
*/
Node nextWaiter;
}
复制代码
构造器
/* 3个构造器 */
Node() { // 用于初始化队列头部
}
Node(Thread thread, Node mode) { // 用于addWaiter 方法
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // 用于Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
复制代码
isShared方法
/**
* 是否是共享机制节点
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
复制代码
predecessor方法
/**
* 获取当前节点的前一个节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
复制代码
核心逻辑追踪
获取独占锁
tryAcquire方法
/**
* 核心获取独占锁资源方法
* 该方法需要子类实现,实现的方式不同,锁的性质也不同
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
复制代码
acquire方法
public final void acquire(int arg) {
// 先尝试获取锁,
// 若获取锁失败则加入队列
// 若获取锁成功则结束。
// tryAcquire方法这里是空方法,需要子类实现
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
addWaiter方法
/**
* 在队尾添加一个节点
*
* @param mode 模式 只用独占和共享两种
*/
private Node addWaiter(Node mode) {
// 根据模式把当前线程封装成一个节点
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail; // 获取最后的线程
if (pred != null) {
node.prev = pred;
// 通过CAS方式插入队尾
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node; // 插入成功则退出
}
}
enq(node); // 通过自旋的方式插入
return node;
}
复制代码
enq方法
/**
* 自旋插入队尾(若队列未初始化则先初始化)
*/
private Node enq(final Node node) {
// 自旋
for (;;) {
Node t = tail;
if (t == null) { // 队列还没初始化
if (compareAndSetHead(new Node())) // 初始化
tail = head;
} else {
node.prev = t;
// CAS插入队尾
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
复制代码
acquireQueued方法
/**
* 尝试竞争锁,竞争失败则继续阻塞
* @return 返回线程是否中断
*/
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;
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire 做阻塞前的准备
// parkAndCheckInterrupt会阻塞,被唤醒后判断线程是否被中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 取消申请锁
cancelAcquire(node);
}
}
复制代码
shouldParkAfterFailedAcquire方法
/**
* 获取锁失败后即将阻塞的准备程序
* 该方法主要是去除前面的取消态节点,并把前一个节点的状态设为SIGNAL。
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 若前一个节点已经是SIGNAL状态,则表示准备完毕
return true;
// 大于0表示前一个节点被取消
if (ws > 0) {
// 用循环把前面都被取消的节点从队列剔除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 把前一个节点设为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
复制代码
parkAndCheckInterrupt方法
/**
* 阻塞当前线程,被唤醒后判断线程是否被中断。
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
复制代码
总结
- 外部调用acquire方法,该方法尝试获取锁,若获取成功则直接返回,获取失败则依次调用addWaiter方法和acquireQueued方法。
- addWaiter把当前线程封装成一个独占节点,并通过CAS方式把节点放入队尾,若插入失败(存在竞争),则自旋插入。返回封装后的线程节点,交给acquireQueued方法。
- acquireQueued会判断该线程节点的前一个节点是否是头节点,若是则再次尝试获取锁,若获取成功则把当前节点设为头节点;失败则依次调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法进入阻塞。
- shouldParkAfterFailedAcquire方法主要是确保当前节点的前一个节点是可被执行的节点,该方法会剔除前面的被取消节点,并把前一个节点通过CAS的方式设为SIGNAL状态,若设置成功则结束。
- parkAndCheckInterrupt方法则直接调用LockSupport的park方法阻塞,被唤醒后会判断当前线程是否被中断,并跳转到第3点。
释放独占锁
tryRelease方法
/**
* 尝试释放锁
* 核心释放独占锁方法
* 该方法需要子类实现,实现的方式不同,锁的性质也不同
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
复制代码
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;
}
复制代码
unparkSuccessor方法
/**
* 唤醒后继者
*/
private void unparkSuccessor(Node node) {
// 把唤醒线程的状态置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // 获取下一个等待线程节点
if (s == null || s.waitStatus > 0) { // 若该节点是空或者被取消
s = null;
// 这里通过尾部遍历获取node节点往后第一个有效的线程节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 激活节点。
}
复制代码
总结
- 外部调用release方法,该方法尝试释放锁,若释放锁成功则尝试通过unparkSuccessor唤醒头节点的后一个节点;释放失败则直接返回false。
- unparkSuccessor方法会找到头节点的后一个有效节点(非取消节点),并通过LockSupport的unpark方法激活该节点。
获取共享锁
tryAcquireShared方法
/**
* 尝试获取共享锁
* 该方法需要子类实现,实现的方式不同,锁的性质也不同
*
* @return 返回值大于0:表示获取锁成功,且会让下一个节点尝试获取锁
* 等于0:则获取锁成功,下一个节点或不会尝试获取锁
* 小于0:获取锁失败
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
复制代码
acquireShared方法
/**
* 获取共享锁
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
// 运行到这表示获取锁失败
doAcquireShared(arg); //加入队列
}
复制代码
doAcquireShared方法
/**
* 尝试竞争共享锁,竞争失败则阻塞
*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (;;) {
final Node p = node.predecessor();
// 只有前一节点是头节点才有资格竞争锁
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置头节点并向后扩散
// 扩散的意思就是继续唤醒后继节点,
// 因为这是共享锁,
// 后继节点有机会在当前节点没有释放锁的前提下获取到锁。
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 做阻塞前的准备,及阻塞,被唤醒后判断是否被中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
setHeadAndPropagate方法
/**
* 设置头节点,并判断是否需要唤醒后继节点
*
* @param node 当前线程节点
* @param propagate 这里其实是剩余的锁资源,若大于0则唤醒后继节点,否则不唤醒
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 把当前节点设为队列头节点
// propagate > 0 表示还有锁资源可以被竞争
// 第二表示之前队列为空
// 第三个条件表示前一个头节点没有被取消
// 第四表示当前队列已为空
// 第五表示当前头节点线程正在运行。
// 这里是比较保守的唤醒,尽量避免有可能竞争锁成功却没有线程竞争的情况。
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 若下一个节点是共享节点
if (s == null || s.isShared())
doReleaseShared(); // 唤醒后继节点,放在释放共享锁时讲解
}
}
复制代码
总结
- 共享锁总体的排队和阻塞机制与独占锁一致,区别是获取到锁后共享锁会调用setHeadAndPropagate方法。
- setHeadAndPropagate方法主要是在把当前节点设为头后,大概率再唤醒后继节点竞争锁,
- 若后继节点竞争锁成功,则后继节点会被设为头节点,并继续唤醒后继节点的后继节点。
- 若后继节点竞争失败,会再次把当前节点状态设为SIGNAL态,并继续阻塞。
释放共享锁
tryReleaseShared方法
/**
* 尝试释放共享锁
*/
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
复制代码
releaseShared方法
/**
* 释放共享锁
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared(); // 唤醒后继节点
return true;
}
return false;
}
复制代码
doReleaseShared方法
/**
* 判断当前节点是否向后扩散过
* 若没有则向后扩散,否则则换成扩散态并结束。
*/
private void doReleaseShared() {
// 自旋
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果头节点是SIGNAL状态,则自旋+CAS设为0状态
// 并唤醒后继节点竞争锁
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}
// 如果状态是0,表示已唤醒过后继节点,则通过自旋+CAS设为扩散态
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 如果头节点没有被改变则结束自旋
if (h == head)
break;
}
}
复制代码
总结
-
释放共享锁主要是调用了doReleaseShared方法。
-
doReleaseShared方法会判断头节点状态,若是为0,则表示已经唤醒过后继节点,直接设为扩散态结束;否则则设为0态,并唤醒后继节点。
可中断锁
acquireInterruptibly方法
/**
* 获取可中断共享锁
*/
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
复制代码
doAcquireInterruptibly方法
/**
* 可中断获取独占锁
* 逻辑基本都和acquireQueue方法相同
* 唯一不同是唤醒后如果被中断了则直接抛异常
*/
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); // 唯一不同的地方。
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg); //
}
复制代码
doAcquireInterruptibly方法
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); // 唯一不同
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
ConditionObject
- 在AQS继承的Lock接口中定义了newCondition方法,该方法返回了的Condition接口。
- ConditionObject是Condition的具体实现类,也是AQS的内部类。
Condition接口
/**
* Condition 类可以使线程互相通信
* 为了简洁,下面只展示了核心的几个方法,一些相似的方法没有展示。
*/
public interface Condition {
/**
* 让当前线程阻塞
*/
void await() throws InterruptedException;
/**
* 唤醒一个阻塞的线程
*/
void signal();
/**
* 唤醒所有阻塞的线程
*/
void signalAll();
}
复制代码
ConditionObject属性
/*
ConditionObject维护了一个双向阻塞队列,即多个调用了await的线程节点
下面两个属性分别表示一头一尾
*/
private transient Node firstWaiter; // 队头
private transient Node lastWaiter; // 队尾
/** 模式意味着在等待退出时重新中断 */
private static final int REINTERRUPT = 1;
/** 模式的意思是在等待退出时抛出InterruptedException */
private static final int THROW_IE = -1;
复制代码
ConditionObject阻塞逻辑追踪
await方法
/**
* 阻塞当前线程
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 把当前线程包装成Condition节点,
// 加入ConditionObject所维护的阻塞队列中
Node node = addConditionWaiter();
// 释放当前线程锁,释放资源
int savedState = fullyRelease(node);
int interruptMode = 0;
// 这里判断节点是否在AQS的队列同步器中
// 队列同步器与Condition的阻塞队列不是同一个队列
// 在队列同步器表示已从阻塞队列移除
// 结束阻塞。
while (!isOnSyncQueue(node)) {
// 运行到这表示不在同步队列器中,
// 即表示还在阻塞队列中
LockSupport.park(this);
// 判断线程是否被中断。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// acquireQueued方法使当前线程参与竞争锁,并返回线程中断状态
// 该方法会阻塞,直到获取锁。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 执行到这表示线程在竞争锁时发生了中断,
// 且当前中断模式不是THROW_IE
interruptMode = REINTERRUPT;
// node.nextWaiter != null 表示阻塞队列不为空
if (node.nextWaiter != null)
// 遍历队列并删除无效的节点
unlinkCancelledWaiters();
// 判断线程是否发生中断
if (interruptMode != 0)
// 根据模式发出线程中断操作
reportInterruptAfterWait(interruptMode);
}
复制代码
addConditionWaiter方法
/**
* 生成ConditionObject的阻塞
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// 若队尾节点是其他状态
if (t != null && t.waitStatus != Node.CONDITION) {
// 把所有非Condition状态的节点移除
unlinkCancelledWaiters();
t = lastWaiter;
}
// 生成对应节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 插入到队尾
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
复制代码
fullyRelease方法
/**
* 释放锁,并返回释放时的锁状态
* 该方法在AQS实现,并不是ConditionObject实现的方法。
*/
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 释放锁
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
// 释放失败,则把节点设为取消状态
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
复制代码
isOnSyncQueue方法
/**
* 判断节点是否是在队列同步器中
* 该方法在AQS实现,并不是ConditionObject实现的方法。
*/
final boolean isOnSyncQueue(Node node) {
// 如果是Condition状态,则不是在AQS队列同步器中
// 因为同步队列器没有Condition状态。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// node.next != null 表示已经在同步队列器中
// 因为ConditionObject的阻塞队列不会用到next属性
if (node.next != null)
return true;
return findNodeFromTail(node);
}
复制代码
findNodeFromTail
/**
* 在同步队列器中从队尾查找指定节点
* 该方法在AQS实现,并不是ConditionObject实现的方法。
*/
private boolean findNodeFromTail(Node node) {
Node t = tail;
// 从尾部遍历
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
复制代码
unlinkCancelledWaiters
/**
* 把所有非Condtion状态节点从阻塞队列移除
*/
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
// 遍历阻塞队列
while (t != null) {
Node next = t.nextWaiter;
// 若不为CONDITION状态,则从队列移除
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
复制代码
reportInterruptAfterWait方法
/**
* 根据模式发出线程中断操作
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
复制代码
总结
- 逻辑梳理
- 外部调用了ConditionObject的await方法。
- await方法会释放当前的锁资源,并生成对应的阻塞节点加入到阻塞队列。
- await方法会阻塞节点,直到节点已被转移到队列同步器。
- 节点被唤醒后,参与竞争锁,最后清理阻塞队列无效的节点。
- await方法潜在调用了release方法,部分实现类如ReentrantLock,在没有获取锁的情况下调用release方法会抛异常,需要注意。
ConditionObject唤醒逻辑追踪
- signal方法
- signalAll方法
signal方法
/**
* 唤醒调用了await方法的线程
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 唤醒第一个等待节点
}
复制代码
doSignal
/**
* 唤醒指定节点
*/
private void doSignal(Node first) {
do {
// 第一个等待线程从当前节点移到当前节点的下一节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// transferForSignal(first)表示把节点从阻塞队列移到队列同步器中
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
复制代码
transferForSignal
/**
* 把节点添加到队列同步器
* 该方法在AQS实现,并不是ConditionObject实现的方法。
*/
final boolean transferForSignal(Node node) {
// 通过CAS转化成0状态
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 把节点插入到队列同步器
Node p = enq(node);
int ws = p.waitStatus;
// 把状态更改为SIGNAL
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 运行到这表示该节点被取消
// 或者插入到了队列同步器中,但状态还不是SIGNAL态
LockSupport.unpark(node.thread);
return true;
}
复制代码
signalAll
/**
* 唤醒所有阻塞线程
*/
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
复制代码
doSignalAll
/**
* 唤醒所有阻塞线程
*/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
// 遍历阻塞队列
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
// 把节点转移到队列同步器
transferForSignal(first);
first = next;
} while (first != null);
}
复制代码
逻辑梳理
- 外部调用signal方法,signal方法调用了doSignal方法。
- doSignal方法把第一个阻塞节点传到transferForSignal方法
- transferForSignal方法把节点从CONDITION状态转化为SIGNAL态,并移到阻塞队列同步器。
- 唤醒流程结束。
- 当节点在AQS队列同步器被唤醒时,处于await方法的阻塞循环中,会尝试获取锁,并继续执行线程的逻辑。