概览
AbstractQueuedSynchronizer 提供原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
使用设计模式——模板方法,子类只需要实现很少几个接口,就可以定一个锁同步类,完成独占锁,共享锁的逻辑。
参考美团点评技术团队编写的:从ReentrantLock的实现看AQS的原理及应用 加上自己的理解来加深对 AQS 的理解。
本文暂时只介绍 AQS 独占加锁与解锁过程实现
ReentrantLock 与 AQS 关联
根据源码可知,ReentrantLock 的非公平锁加锁,只用实现 tryAcquire 获取锁的逻辑,就可以完成非公平的独占锁获取。其中获取锁失败进行等待的逻辑 AQS 已经帮我们实现。
1. 独占加锁 acquire 实现
// AQS acquire 方法
public final void acquire(int arg) {
// 首先调用子类 tryAcquire 去获取锁,若获取锁失败,才执行后面的 addWaiter acquireQueued 逻辑
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 获取锁成功,当前线程中断
}
主要方法:
- tryAcuire,子类重写获取锁的逻辑
- addWaiter,添加节点到队列末尾
- enq ,入队以及初始化队列
- acquireQueued,返回 node 在等待过程中是否被中断过
- shouldParkAfterFailedAcquire,阻塞获取锁失败的线程,删除队列中状态为取消的节点
- parkAndCheckInterrupt,真正的阻塞办法
- cancelAcquire,获取锁过程中发生了异常
1.1 tryAcquire
子类复写 tryAcquire 方法,里面实现获取锁逻辑,获取成功返回 true,获取失败返回 false。
参考 ReentranLock 灯同步类的实现,主要逻辑是 setState ,并将当前线程标记为已获取到该锁
1.2 addWaiter
将节点插入到队列尾部,若队列为初始化,先进行初始化
// 将 EXCLUSIVE 类型的 Node 添加到队列尾部
private Node addWaiter(Node mode) {
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 的前置指针指向 pred
if (compareAndSetTail(pred, node)) { // 这个方法就是将 tailOffset 的值与 pred 进行比较,若相同,将 tailOffset 的值设为 node,即将 tail 指向 node。
pred.next = node; // pred.next 指向 node
return node; // 返回刚刚添加的 node
}
}
// 尾节点为空(队列中没有元素,需要初始化)
// 或者 compareAndSetTail 失败(tailOffset 处的值不是 pred,被其它线程修改,导致 tail 指向的节点和 pred 指向的节点不同)
enq(node); // 死循环方式 插入到队列中
return node; // 返回刚刚添加的 node
}
// 插入节点到队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾指针为 null
if (t == null) { // Must initialize
if (compareAndSetHead(new Node())) // 队列初始化
tail = head; // ,head 和 tail 指向同一处,进行下一次循环
} else {
// 队列中有元素
node.prev = t; // node 的前置指针指向 tail
if (compareAndSetTail(t, node)) { // 若 tailOffset 指向的值等于 t,将 tail 处的值改为 node
t.next = node; // 队列是双向的,将后置指针指向 node
return t; // 返回插入队列前 队列中的最后一个节点
}
}
}
}
1.3 acquireQueued
循环从队列中查询 node 节点能否获取锁,直到线程获取成功或者被中断
// node 是刚刚插入队列结尾的节点,死循环去判断 node 是否获取到锁,返回是否被中断
final boolean acquireQueued(final Node node, int arg) {
// 获取失败标志位
boolean failed = true;
try {
// 是否被中断
boolean interrupted = false;
// 死循环,直到获取锁成功
for (;;) {
// 刚刚插入节点 node 的前置节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 若前置节点是头节点(头节点里面啥都没有),并且当前 Node 获取锁成功
setHead(node); // head 指针后移指向 node,并将 node 属性置为 null
p.next = null; // help GC
failed = false;
return interrupted;
}
// 上面条件不满足:p 不是头节点 或者 node 没有获取到锁
// shouldParkAfterFailedAcquire 若锁获取失败,返回线程是否需要被阻塞
// parkAndCheckInterrupt 阻塞当前线程(for 循环卡住),并返回当前线程是否被中断
// 这个方法块的意思:循环判断 node 是否需要被阻塞,并进行阻塞直到被唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 被中断过
interrupted = true;
}
} finally {
// 出现不正常情况,取消获取锁
if (failed)
cancelAcquire(node); // 后面讲
}
}
1.3.1 shouldParkAfterFailedAcquire
// waitStatus 状态列举:
static final int CANCELLED = 1; // 取消状态
static final int SIGNAL = -1; // 后继线程需要解锁
static final int CONDITION = -2; // 等待某个条件的状态
static final int PROPAGATE = -3; // 获取共享锁传播状态
// 根据前驱节点 pred 判断 node 是否需要被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//
int ws = pred.waitStatus;
// 前驱节点处于 唤醒状态,
if (ws == Node.SIGNAL)
return true; // 那么返回需要被阻塞,被唤醒下面说
// 若前驱节点处于取消状态,移除队列中 node 前面处于取消状态的节点
if (ws > 0) {
do {
// 往前遍历,直到找到队列中,节点不是 取消状态 的节点,将 node 的前置指向指向这个节点。
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 取消状态的节点,就从队列中移除了
pred.next = node;
} else {
// 将前置节点的 waitStatus 设为 SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 返回不需要被阻塞
return false;
}
1.3.2 parkAndCheckInterrupt
// 挂起当前线程,并返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
1.3.3 cancelAcquire
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0) // 往前遍历,跳过 waitStatus = CANCELLED 的节点
node.prev = pred = pred.prev;
Node predNext = pred.next; // 删除中间 waitStatus = CANCELLED 的节点,pred.next 指向 node
// 将 node 状态改为 CANCELLED
node.waitStatus = Node.CANCELLED;
// 如果 node 是尾节点,直接删除末尾的 node
if (node == tail && compareAndSetTail(node, pred)) {
// pred 指向 predNext,predNext 为空节点
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 当 pred 不是头节点,并且 pred.waitStatus == SIGNAL 时
// 或者 pred 不是头节点,并且 pred.waitStatus 不是 CANCELLED 状态,将 pred.waitStatus 改为 SIGNAL
// 以上两种情况出现时,将 pred.next 指向 node.next。即删除 pred 指向 node 的指针,直接指向 node.next
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next); //
} else {
// 上面条件不满足,即:
// pred 是头节点
// pred 状态为 CANCELLED
// pred.thread == null
// 以上三种情况出现时,唤醒 node 后面阻塞的线程
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
// 唤醒 node 后面的线程
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) // 不是 CANCELLED 状态时,把 node.waitStatus 设为 0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 若 node 的后继节点为空,或者后继节点时 CANCELLED
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点开始往前遍历,找到队列中从左往右第一个不是 CANCELLED 状态的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒该节点
LockSupport.unpark(s.thread);
}
- 从尾节点开始往前遍历,找到第一个非 CANCELLED 节点的原因
addWaiter 方法节点入队 node.prev = pred; compareAndSetTail(pred, node) 不是原子操作,所以 pre 指针是完整的
其次,删除 CANCELLED 节点时,先删除的时 NEXT,Prev 没有改,所以可以完整遍历整个队列
1.4 acquire 方法总结
acquire 方法的总体思路:
- tryAcquire 子类实现获取锁逻辑,获取成功,方法结束
- 获取失败,通过 addWaiter 加入到队列结尾,并初始化未初始化的队列
- acquireQueued 循环判断 node 节点是否能获取到锁,里面做了几件事:
- 当 node 节点前置节点是 head 并且再次尝试获取锁成功,直接返回
- 判断获取失败的 node 节点是否需要被阻塞,里面会去删除队列中 waitstatus 为 CANCELLED 状态的节点,并返回线程的中断状态,若被阻塞,acquireQueued 中的 for 循环将被卡住,等待被唤醒
- 获取锁期间发生异常,将队列中的 node 删除
- 跳出 acquireQueued 循环的条件是 node 前置节点是 head 并且获取到锁,否则进行阻塞等待
2. 独占解锁实现
释放锁,调用 子类重写的 tryRelease 方法,若解锁成功,将唤醒 head 后面的 node
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 当头指针已经初始化
// waitStatus == 0,表示后继节点正在运行中,不需要唤醒
// waitStatus < 0,表示节点状态不是 CANCELLED,那么需要被唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
2.1 tryRelease
ReentranLock tryRelease 释放锁实现,公平锁和非公平锁都一样
protected final boolean tryRelease(int releases) {
// 减少可重入次数
int c = getState() - releases;
// 当前线程不持有该锁,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 持有锁全部释放
if (c == 0) {
free = true;
// 将当前锁的所占线程置为 null
setExclusiveOwnerThread(null);
}
// 更新 state
setState(c);
return free;
2.2 unparkSuccessor
unparkSuccessor 在上面 1.3.3 有介绍
3. 可中断加锁实现
逻辑基本与 acquire 一致,唯一不同的地方就是阻塞等待获取锁过程中,如果被中断,直接抛出异常。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 如果当前线程被中断,直接抛出
if (Thread.interrupted())
throw new InterruptedException();
// 子类获取锁失败
if (!tryAcquire(arg))
// 执行具体的可中断的加锁逻辑
doAcquireInterruptibly(arg);
}
// 这个方法和 acquireQueued 基本一样,唯一不同的就是等待过程中,线程被中断直接抛出(parkAndCheckInterrupt 返回 true)
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())
// 与 acquireQueued 不同点
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4. 超时等待式获取锁
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout); // 子类获取锁成功 或者 doAcquireNanos 获取成功
}
// 一个时间阈值,若时间小于该值,不阻塞当前线程而是继续获取,提高响应速度
static final long spinForTimeoutThreshold = 1000L;
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 超时时间点
final long deadline = System.nanoTime() + nanosTimeout;
// 添加到队列尾部
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 true;
}
// 计算与截止时间的差距
nanosTimeout = deadline - System.nanoTime();
// 已经超时,直接返回 false
if (nanosTimeout <= 0L)
return false;
// 阻塞获取锁并且差距要大于 1000,
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
// 指定阻塞指定时间
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}