一、核心属性
AQS 的内部状态主要依靠以下几个关键字段维护:
| 属性 | 类型 | 说明 |
|---|---|---|
state | volatile int | 同步状态。这是 AQS 最核心的字段,代表共享资源的数量。在 ReentrantLock 中表示锁被持有的次数(可重入),在 Semaphore 中表示剩余许可数。对 state 的操作必须保证线程安全。 |
head | volatile Node | 等待队列的头结点。头结点是一个“哨兵节点”,它本身不持有等待的线程,其 next 节点(head.next)才是队列中第一个真正等待的线程节点。 |
tail | volatile Node | 等待队列的尾结点。每次有新线程加入队列时,都会通过 CAS 方式更新 tail 指向新的尾节点。 |
exclusiveOwnerThread | Thread | (继承自 AbstractOwnableSynchronizer)持有独占锁的线程。在独占模式下,用来记录当前占有同步状态的线程,用于实现可重入等特性。 |
关于 state 的三个操作模板方法
AQS 提供了三个 protected final 的方法来操作 state,子类可以通过它们来修改同步状态:
int getState():获取当前同步状态。void setState(int newState):设置当前同步状态。boolean compareAndSetState(int expect, int update):使用 CAS 原子性地设置同步状态,保证并发修改的安全性。
二、重要内部类:Node
Node 是构成 AQS 等待队列的基本单位,它是一个双向链表的节点。使用双向链表可以方便地删除中间节点(如取消的节点),以及从后向前遍历。
Node 的核心属性
| 属性 | 类型 | 说明 |
|---|---|---|
prev | volatile Node | 前驱节点。 |
next | volatile Node | 后继节点。 |
thread | volatile Thread | 当前节点所持有的线程。 |
waitStatus | volatile int | 节点的等待状态,共有以下 5 种: |
CANCELLED (1) | 表示线程已取消(因超时或中断)。处于此状态的节点不会再被唤醒。 | |
SIGNAL (-1) | 表示当前节点的后继节点需要被唤醒(即当前节点释放锁或取消时,必须唤醒后继节点)。 | |
CONDITION (-2) | 表示当前节点在条件队列(Condition)中等待。 | |
PROPAGATE (-3) | 仅在共享模式下使用,表示下一次共享式获取应该无条件传播下去。 | |
| `0 | 初始状态,表示当前节点在同步队列中,等待获取锁。 | |
nextWaiter | Node | 在条件队列中指向下一个等待节点;在同步队列中,也可用于标记模式(独占或共享)。 |
节点模式
static final Node SHARED = new Node():标记节点为共享模式。static final Node EXCLUSIVE = null:标记节点为独占模式。
三、核心方法源码解析
1. acquire(int arg):独占获取的入口
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
解析:
- 首先尝试 tryAcquire,成功则直接返回。
- 失败则通过 addWaiter 入队,然后 acquireQueued 使线程在队列中等待。
- 如果 acquireQueued 返回 true(表示等待过程中被中断过),则调用 selfInterrupt() 在获取成功后补上中断标志。
2. addWaiter(Node mode):将线程加入等待队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速入队:直接 CAS 设置尾节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速入队失败,则通过 enq 方法自旋入队
enq(node);
return node;
}
解析:
- 首先创建一个持有当前线程的节点,模式由参数 mode 指定(独占或共享)。
- 快速路径: 如果队列已存在(tail != null),尝试直接 CAS 更新尾节点。若成功,则双向链接完成,直接返回节点。这是最常见的无竞争情况,效率很高。
- 完整路径: 如果队列为空或 CAS 失败(说明有其他线程也在同时入队并修改了 tail),则调用 enq(node) 自旋入队。
3. enq(Node mode): 自旋入队
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列为空,需要初始化
if (compareAndSetHead(new Node())) // 设置头节点(哨兵节点)
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq 通过自旋不断尝试,直到入队成功。它处理了两种场景:
如果队列为空(tail == null),先初始化一个头节点(哨兵节点),然后再次循环。
如果队列非空,则执行与快速入队相同的 CAS 设置尾节点的操作,直到成功。
4. acquireQueued(Node node, int arg):队列中线程的自旋与阻塞
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; // 断开原头节点,帮助 GC
failed = false;
return interrupted;
}
// 如果不是头节点或获取失败,判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; // 记录中断,但不退出
}
} finally {
if (failed)
cancelAcquire(node); // 如果发生异常,取消获取
}
}
解析:
当前线程进入队列后,在 for(;;) 中自旋。
**检查前驱:**如果前驱是头节点(即当前节点是队列中第一个等待线程),则再次调用 tryAcquire 尝试获取锁。这是 FIFO 公平性的体现。
获取成功: 将自己设为新的头节点,断开原头节点的引用,返回中断标志。
获取失败: 调用 shouldParkAfterFailedAcquire 检查并设置前驱节点的状态。如果前驱状态为 SIGNAL,则当前线程可以安全地阻塞;否则会跳过取消的节点或尝试将前驱设为 SIGNAL 后重试。
阻塞: 当 shouldParkAfterFailedAcquire 返回 true 时,调用 parkAndCheckInterrupt() 通过 LockSupport.park(this) 阻塞当前线程。唤醒后检查是否被中断,并记录中断标志。
中断处理: acquireQueued 不响应中断,只记录中断标志,最终返回给上层,由上层(如 acquire)决定是否补上中断。
5. release(int arg):释放独占资源
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 尝试释放资源(由子类实现,例如 ReentrantLock 中减少 state)。
如果释放成功,检查头节点是否有效(waitStatus != 0 表示有后继需要唤醒),然后调用 unparkSuccessor 唤醒后继线程。
unparkSuccessor 会找到头节点的下一个未取消节点,并使用 LockSupport.unpark(thread) 唤醒它。
6、shouldParkAfterFailedAcquire(): 判断是否应该进入阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 获取前驱节点的等待状态
if (ws == Node.SIGNAL) // 情况1:前驱已设置唤醒信号
return true; // 当前线程可以安全地阻塞
if (ws > 0) { // 情况2:前驱已取消(CANCELLED)
do {
node.prev = pred = pred.prev; // 跳过所有连续取消的前驱
} while (pred.waitStatus > 0);
pred.next = node; // 重新建立链接
} else { // 情况3:前驱状态为 0 或 PROPAGATE
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 尝试将前驱设为 SIGNAL
}
return false; // 返回 false,表示不能阻塞,需要重试
}
1. 前驱状态为 SIGNAL(-1) :前驱节点已经承诺:当它释放锁或被取消时,会负责唤醒当前节点,返回 true。
2. 前驱状态为 CANCELLED(>0):该节点已因超时或中断而被取消。取消的节点不应再作为唤醒的责任节点,因此需要跳过所有连续取消的前驱,直到找到一个未取消的节点。
3. 前驱状态为 0 或 PROPAGATE(-3) : 如果前驱状态为初始状态 0(表示节点刚入队,还未设置任何信号),或者是共享模式下的 PROPAGATE,则需要通过 CAS 将前驱状态设置为 SIGNAL,以表明当前节点需要被唤醒。
设置成功后,返回 false。为什么返回 false?因为设置状态后,当前线程还不能立即阻塞,需要再次尝试获取锁,以防在设置状态期间锁已经被释放。如果此时获取成功,就无需阻塞了。