有了上一节的手写迷你版的ReentrantLock,我们大致对AQS有了一些认识,接下来看一下AQS内部的一些结构。
1.AQS内部结构
1.1核心内部类Node
尝试获取锁失败的线程会被构造成一个Node节点去队列(双向链表结构)
中排队,我们看一下Node的结构。
static final class Node {
//共享模式
static final Node SHARED = new Node();
//独占模式
static final Node EXCLUSIVE = null;
/*
* 下面的这几个int类型的常量表示节点的状态值
*/
//表示当前节点处于取消状态
static final int CANCELLED = 1;
/*
* 表示当前节点需要唤醒它的后继节点,(signal表示的其实是后继节点的状态,需要当前节点去唤醒它。。。)
*/
static final int SIGNAL = -1;
/*
* 等待队列(Condition)中的节点状态为 -2
*/
static final int CONDITION = -2;
//ReentrantLock没有用到 后面将Condition队列时再讲
static final int PROPAGATE = -3;
/*
* 表示node的状态,可选值(0, SIHGNAL(-1), CANCLLED(1),)
* waitStatus = 0 默认状态
* waitStatus > 0 取消状态
* waitStatus = -1 表示当前Node如果是head节点时 释放锁之后需要唤醒后继节点
*/
volatile int waitStatus;
//Node的前驱节点
volatile Node prev;
//Node的后继节点
volatile Node next;
//Node内部封装的线程。
volatile Thread thread;
//ReentrantLock没有用到。
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.2核心属性
//队列头结点,任何时刻头结点对应的线程都是当前持锁线程。
private transient volatile Node head;
//队列尾节点
private transient volatile Node tail;
/*
* 核心属性:表示资源
* 独占模式下:0表示未加锁 >0表示加锁状态
*/
private volatile int state;
/*
* 继承父类的属性。
* 独占模式下表示当前持有锁的线程。
*/
private transient Thread exclusiveOwnerThread;
2.Lock过程(公平锁为例)
2.1详细流程图
尝试获取锁+构造节点入队过程
在队列中被挂起+被唤醒重新抢锁的过程
2.2AQS.acquire()
//AQS.acquire()
public final void acquire(int arg) {
/*
* 1.tryAcquire()尝试获取锁,获取成功返回true,获取失败返回false。
* 2.
* 2.1 addWaiter() 获取锁失败将当前线程封装成一个Node入队
* 2.2 acquireQueued(),在队列中尝试获取锁的过程,返回值表示挂起过程中线程
* 是否是被中断唤醒的,false表示不是被中断唤醒的。
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//设置中断标志位为true。
selfInterrupt();
}
2.3ReentrantLock.tryAcquire()
protected final boolean tryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取state的值(即当前锁的状态)
int c = getState();
//c == 0表示当前处于无锁状态。
if (c == 0) {
/*
* 因为这里是公平锁,所以必须先查看队列中是否有节点,有节点就得去排队,不允许竞争锁。true表示队列中有节点
* false表示队列中没有节点
* hasQueuedPredecessors()查看队列中是否有节点。
*
* 只有当队列中没有节点时才能使用CAS操作去获取锁,
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//抢锁成功 将当前线程设置为获取锁的线程
setExclusiveOwnerThread(current);
//直接return true。
return true;
}
}
/*
* 走到这里就说明当前锁已经被占用了,因为ReentrantLock是可重入锁,所以会判断
* 持有锁的线程是否就是当前线程,即这里是重入的逻辑。
*/
else if (current == getExclusiveOwnerThread()) {
//重入的逻辑
int nextc = c + acquires;
//这里判断 nextc < 0,是判断是否超出了int最大值。
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//设置新的state,setState是一个无锁方法,因为只有持锁线程才能进到这里
setState(nextc);
//重入锁再次加锁成功 也返回true。
return true;
}
/*
* 什么时候返回false。
* 1. state = 0,无锁状态,但是CAS抢锁失败(有并发)
* 2. state > 0, 且持锁线程非当前线程。
*/
return false;
}
}
2.4AQS.addWaiter()
//最终会返回当前节点
private Node addWaiter(Node mode) {
//将当前线程封装成一个Node节点,mode是独占模式(ReentrantLock)
Node node = new Node(Thread.currentThread(), mode);
//获取尾节点
Node pred = tail;
if (pred != null) {
//将当前节点的前驱指向pred
node.prev = pred;
//CAS设置tail指向node (这里可能有并发,所以要使用CAS操作)
if (compareAndSetTail(pred, node)) {
//CAS成功,将尾结点的后继指向node
pred.next = node;
//说明入队成功,返回node即可。
return node;
}
}
/*
* 什么时候会执行到这里?
* 1. 当前队列是空队列,tail == null
* 2. CAS竞争入队失败。。会来到这里
*
* enq()方法,不断自旋入队。
*/
enq(node);
return node;
}
2.5AQS.enq()
private Node enq(final Node node) {
//自旋,保证一定可以入队,只有入队才会跳出循环
for (;;) {
Node t = tail;
/*
* 当前队列是空队列,tail == null
* 说明当前锁被占用,但是队列没有节点,说明当前线程可能是第一个获取锁失败的线程(存在并发)
* ,那么作为当前持锁线程的第一个后继线程,需要做什么?
*
* 因为第一个当前持锁的线程,获取锁时直接tryAcquire成功了,没有向阻塞队列中条件任何的node,
* 所以作为后继需要为它补一个Node
*/
if (t == null) {
// Must initialize
//CAS向队列中追加一个Node 然后继续自旋
if (compareAndSetHead(new Node()))
tail = head;
} else {
//CAS入队。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
2.6AQS.acquireQueued()
/*
* acquireQueued()方法的作用?
* 1.当前节点入队后有没有被挂起呢?
* 2.如果被挂起了? 那么唤醒之后的逻辑在哪?
* 都在此方法中。
*
* @param node 表示当前封装的node
* @param arg 表示加锁的arg参数
*/
final boolean acquireQueued(final Node node, int arg) {
/*
* true 表示当前线程抢占锁成功 (一般情况下,最终肯定会获取到锁)
* false 表示失败,需要执行出队的逻辑(后面将响应中断的lock方法时再讲)
*/
boolean failed = true;
try {
//表示当前线程是否被中断。
boolean interrupted = false;
//自旋 获取锁
for (;;) {
/*
* 什么情况会执行这里?
* 1.进入for循环时,在线程尚未park前
* 2.线程park后,被唤醒后,也会执行这里(自旋)
*/
//拿到节点的前驱节点
final Node p = node.predecessor();
/*
* 这里判断当前节点是不是head.next节点
* 条件成立的话才有机会调用tryAcquire()尝试获取锁
*/
if (p == head && tryAcquire(arg)) {
//拿到锁之后,设置头结点为当前节点,将前驱节点出队。
setHead(node);
p.next = null; // help GC
//没有发生异常
failed = false;
//返回当前线程的中断标记。
return interrupted;
}
/*
* shouldParkAfterFaildAcquire():当前线程获取锁失败后,是否需要挂起?
* true -> 需要挂起 | false -> 不需要挂起
* -------
* parkAndCheckInterrupt()将当前线程挂起,被唤醒后返回中断标志位(会清除中断标志位)
* 返回当前线程是否是被中断唤醒的。
*
* (唤醒 :1.正常唤醒 其他线程unpark 2.其他线程给当前挂起的线程一个中断信号)
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果当前线程是被中断唤醒的,就将interrupted置为ture。
interrupted = true;
}
} finally {
if (failed)
//取消竞争。
cancelAcquire(node);
}
}
//----------------------------------------------------------------------------
private final boolean parkAndCheckInterrupt() {
//使用LockSupport挂起当前线程
LockSupport.park(this);
//返回当前线程是否被中断,该方法会清除线程的中断标志,
return Thread.interrupted();
}
2.7AQS.shouldParkAfterFailedAcquire()
总结:
- 1.当前节点的前驱节点是cancel状态,第一次来到方法时,会
将前面所有处于cancel的节点全部删除
,最终找到第一个状态是0的节点,然后将其状态设置为**-1(SIGNAL)**,然后继续自旋后返回true。 - 2.当前节点的前驱节点状态是0,当前线程会设置前驱节点的状态为-1,然后再次自旋时会返回true。
主要做的事就是删除当前节点前面连续的所有处于cancel的节点,找到第一个状态为0的节点,将其状态设置为-1(SIGNAL)
.然后退出。
/*
* @param pred 当前线程node的前置节点
* @param node 当前线程对应的node
* @return true -> 当前线程需要挂起 false -> 当前线程不需要挂起
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/*
* 获取当前节点前驱节点的状态(waitStatus).
* waitStatus = 0 初始默认状态
* > 0 表示节点时cancel取消状态
* -1 (SIGNAL)表示当前节点释放锁后会唤醒它的第一个后继节点。
*/
int ws = pred.waitStatus;
//表示当前节点的前驱节点状态就是SIGNAL,直接return true。
if (ws == Node.SIGNAL)
return true;
/*
* ws > 0 表示当前节点node的前驱节点的waitStatus > 0是取消状态,取消状态的
* 节点无法唤醒后继节点,所以需要一直向前找到第一个waitStatus <= 的节点。
* 在这个过程中,会将waitStatus > 0的节点全部删除。
*/
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//找到的第一个ws <= 0的节点的next指针指向当前node。
pred.next = node;
} else {
// 到这里 说明 ws = 0,
//将当前线程node的前置节点强制设置为SIGNALE,表示前置节点释放锁之后需要唤醒我
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}