AQS源码分析和图解
最近在研究AQS源码的时候,找了一些网上的博客和论坛,发现描述的都大同小异,每个人的说法都不太完全统一,后来找了一些网上推荐的一些书籍,书籍介绍的知识点倒还挺全面的,但是,自己看书的话,书本知识点不容易串联起来,理解起来有点吃力,于是便自己亲自调试源码,并写下本博客,希望大家可以参考参考,有什么不对的地方欢迎大家留言指正。
本文调试的代码如下:
public class ConditionTest {
static final Lock lock = new ReentrantLock();
static final Condition condition = lock.newCondition();
public static void main(String[] args) throws Exception{
final Thread thread1 = new Thread("Thread 1 "){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 准备获取锁 .....");
lock.lock(); // 线程 1获取 lock
try {
System.out.println(Thread.currentThread().getName() + " 准备调用await(等待一个signal) ..... ");
condition.await(); // 调用 condition.await 进行释放锁, 将当前节点封装成一个 Node 放入 Condition Queue 里面, 等待唤醒
System.out.println(Thread.currentThread().getName() + " 获得一个 signal,await调用结束 ..... ");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 准备调用unlock ");
lock.unlock(); // 释放锁
}
};
thread1.start(); // 线程 1 线运行
Thread.sleep(4 * 1000);
Thread thread2 = new Thread("Thread 2 "){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 准备获取锁 .....");
lock.lock(); // 线程 2获取lock
System.out.println(Thread.currentThread().getName() + " 准备中断 [Thread 1] .....");
thread1.interrupt(); // 对线程1 进行中断 看看中断后会怎么样?
try {
Thread.sleep(10 * 1000);
}catch (Exception e){
}
System.out.println(Thread.currentThread().getName() + " 准备发送一个 signal 信号");
condition.signal(); // 发送唤醒信号 从 AQS 的 Condition Queue 里面转移 Node 到 Sync Queue
System.out.println(Thread.currentThread().getName() + " 发送 signal 结束,准备unlock");
lock.unlock(); // 线程 2 释放锁
}
};
thread2.start();
Thread.sleep(4 * 1000);
Thread thread3 = new Thread("Thread 3 "){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 准备获取锁 .....");
lock.lock(); // 线程 3 获取 lock
try {
System.out.println(Thread.currentThread().getName() + " 准备调用await(等待一个signal) ..... ");
condition.await(); // 调用 condition.await 进行释放锁, 将当前节点封装成一个 Node 放入 Condition Queue 里面, 等待唤醒
System.out.println(Thread.currentThread().getName() + " 获得一个 signal,await调用结束 ..... ");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 准备调用unlock ");
lock.unlock(); // 释放锁
}
};
thread3.start();
}
}
本文分成三大部分,分别是源码调试和图解、源码分析、归纳总结
1. 源码调试和图解
以下截图是本地调试的结果,分为代码断点截图(如图1-1所示)和AQS状态图(如图2所示),AQS状态图是程序运行到断点时,根据各参数情况绘制的,每个AQS状态图和上一个AQS状态图的变化均用红色笔迹标识,如图2和图4红色字迹的变化。本文约定,LockSupport.park()方法表示线程阻塞挂起,LockSupport.unpark()表示线程唤醒
Thread 1获取独占锁,并且把状态state赋值为1,此时AQS状态信息表示Thread 1获取了独占锁。
Thread 2获取独占锁失败。(刚才被Thead 1赋值了信息,也就是被Thread 1获取了锁)
Thread 2创建锁队列节点(首节点,代表Thread 1),并把自己的Thread 2节点加入锁队列。
2 源码分析
根据第一部分的AQS状态变化,进行源码分析:
(1)首先,分析Node节点,锁队列节点和等待队列节点都是使用这个数据结构
Node源码分析
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;// 标志等待超时或被中断
static final int SIGNAL = -1;// 表示释放锁之后,是否通知后一个节点
static final int CONDITION = -2;// 表示节点在等待队列中
static final int PROPAGATE = -3;// 共享模式中使用,运行状态
volatile int waitStatus; // 表示等待状态,有CANCELLED 、SIGNAL、PROPAGATE 、PROPAGATE 和 0
volatile Node prev; // 表示上一个结点的引用
volatile Node next; // 表示后继结点的引用
volatile Thread thread; // 当前结点的线程名称
Node nextWaiter; // 代表下一个等待队列结点的引用,在等待队列中有用
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取上一个结点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//创建首结点和共享结点
Node() {}
// 用于创建锁队列结点
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
//创建等待队列节点
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
(1)addWaiter方法和enq方法用于把新结点加入锁队列
addWaiter源码分析
//将当前线程加入到锁队列的尾部,并返回当前线程所在的结点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//EXCLUSIVE(独占)和SHARED(共享)
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {// 锁队列有结点时候,直接放到队尾。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);// 锁队列没有结点时候的添加方法
return node;
}
enq源码分析
// 锁队列没有结点时候的入队方法
private Node enq(final Node node) {
for (;;) { // CAS + 循环 = 自旋
Node t = tail;
if (t == null) { // 队列为空
if (compareAndSetHead(new Node()))// 创建一个空的标志结点作为head结点
tail = head; // tail 和 head都是同一个节点
} else {
node.prev = t; // 新节点的前继结点指向尾结点
if (compareAndSetTail(t, node)) { // 把尾节点指向新结点
t.next = node; // 添加前的尾结点指向新加的结点
return t;
}
}
}
}
(2)acquireQueued方法用于标记新增节点的前继结点为SIGNAL状态(shouldParkAfterFailedAcquire方法)、阻塞(parkAndCheckInterrupt方法)、循环判断前继结点是否头结点并获取到锁
acquireQueued源码分析
// 进入park状态,直到其他线程释放资源后唤醒继续执行
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)) { // 如果前一个节点是head,尝试抢一次锁
setHead(node); // 更换head
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&// 检查状态,把前继结点标志为SIGNAL
parkAndCheckInterrupt())// 用于阻塞
interrupted = true; // 如果出现中断,则修改标记
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckInterrupt源码分析
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞挂起
return Thread.interrupted(); // 唤醒,返回是否有中断迹象
}
shouldParkAfterFailedAcquire源码分析
//检查状态,是否需要挂起线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 根据 前置节点的状态 执行不同的流程
if (ws == Node.SIGNAL) // 前置节点释放锁之后会通知当前线程,则后续会进行阻塞
return true;
if (ws > 0) { // 前置节点处于CANCELLED状态,跳过它继续寻找正常的节点,并且去掉中间那段不正常的节点
do { // 也可以理解为,这是一次队列检查
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//把前继结点标记为SIGNAL,前继节点释放锁后唤醒本节点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
(3)await源码解析
// 等待队列新增结点,释放锁状态,首结点的下一个结点唤醒并重置为首结点
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();// 等待队列中新增结点
int savedState = fullyRelease(node);// 释放当前获取的锁,从头到尾把锁队列的waitStatus位置0,并唤醒后继结点
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 释放在锁队列
LockSupport.park(this); // 等待队列的新增节点阻塞挂起
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//把等待队列的结点waitstatus置为0,并加入到锁队列,此时并存在两个队列
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // 不断自旋,等待被唤醒(在锁队列中阻塞挂起)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 清除cancell的结点
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
(4)doSignal源码解析
// 循环一遍等待队列,不断把等待队列的waitstatus置为0
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal源码解析
final boolean transferForSignal(Node node) {
// 把等待队列的waitstatus置为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 加入到锁队列,返回前继结点
int ws = p.waitStatus; // 前继节点是CANCELL或者重置SIGNAL失败,则唤醒当前结点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
(5)release源码解析
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试把exclusiveOwnThread和state重置初始状态
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}
unparkSuccessor源码解析
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);// 当前结点的waitstatus置为0
Node s = node.next;
// 寻找后继有用的结点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒后续有用的结点
}
- 归纳总结
(1)获取锁的时候,ExclusiveOwnThread和state对应线程信息,tryAcquire和tryRelease对应修改这两个字段的信息,分别尝试获取锁和释放锁
(2)addWaiter方法和enq方法用于锁队列结点的添加,并更新head和tail指针
(3)acquireQueued方法作用:把前继节点的waitStatus赋值为SIGNAL,阻塞挂起(唤醒加锁并把自己设置为锁队列首节点)。
(3)await方法作用:等待队列加入新结点、释放锁并唤醒head的后继结点尝试获取首节点、等待队列首结点阻塞挂起(唤醒:等待队列首结点被唤醒并把结点转移到锁队列尾部并唤醒、把尾结点的前继节点waitStatus赋值为SIGNAL、锁队列尾结点挂起阻塞)
(4)signal方法作用:把等待队列首节点转移到锁队列尾结点,并把waitstatus赋值为0
(5)release方法作用:释放exclusiveThread和state的信息