文章参考:(4条消息) AQS源码探究_03 成员方法解析(加锁、资源竞争逻辑)_兴趣使然的草帽路飞-CSDN博客
该文章中有些错误,我进行了改正。
AQS成员方法解析
1.lock加锁方法
这里是ReentrantLock的公平锁的方法实现 在ReentrantLock的FairSync静态内部类中
final void lock() {
acquire(1);
}
2.acquire令当前线程竞争资源的方法
//位于AQS下的acquire
public final void acquire(int arg) {
//条件1 !tryAcquire(arg) 尝试去获取锁 获取成功返回true 取反为false后面的判断就不用再执行 获取失败的情况会返回false 取反会返回true 会接着执行后面的判断
//addWaiter方法 将当前线程封装成node入队 如果线程尝试获取锁未获取到 就会将当前线程加入阻塞队列中 如果获取到锁就不会执行这个代码
//入队列后调用acquireQueued()方法 (该方法包含挂起当前线程,以及线程唤醒后相关逻辑)
// 令当前线程不断的去竞争资源 直到成功才停止自旋
// acquireQueued方法返回boolean类型 true:表示挂起过程中线程中断唤醒过 false:表示未被中断唤醒过
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.tryAcquire尝试获取锁的方法(公平锁方法实现)
//位于ReentrantLock类的静态内部类FairSync中:是ReentrantLock中公平锁的tryAcquire实现
//返回true 尝试获取锁成功 返回false 尝试获取锁失败
protected final boolean tryAcquire(int acquires) {
//获取到当前线程
final Thread current = Thread.currentThread();
// AQS中的state(加锁状态)值
int c = getState();
//如果条件成立,c==0 表示当前的AQS处于无锁的状态 无锁的情况下
//该方法主要是尝试去获取锁 会判断队列中是否为空
if (c == 0) {
//因为此方法是公平锁的实现,任何时候都要检查一下当前线程之前,队列中是否有等待者
//条件1 hasQueuedPredecessors() 判断FIFO队列中是否为空
//true ->表示当前线程前面有等待者线程 当前线程需要入队等待 取反的情况下为false后面的判断就不用执行
//false ->表示当前线程前面没有等待者线程 直接可以尝试获取锁 取反为true 后面的判断会继续执行
if (!hasQueuedPredecessors() &&
//条件2:compareAndSetState(0,acquires)基于CAS去更新state的值
//state更新成功:说明当前线程抢占锁成功
//state更新失败,说明多个线程存在竞争,当前线程竞争失败,未能抢到锁的持有权
compareAndSetState(0, acquires)) {
//条件1和2都成立时,说明当前线程抢夺锁的持有权成功
//抢占到锁后都需要做写什么呢? 设置当前线程为独占线程(锁的持有者)
setExclusiveOwnerThread(current);
//true -> 当前线程尝试获取锁成功
return true;
}
}
//这里是c != 0的逻辑 说明当前有线程持有锁
//这里是判断当前线程是否是持有锁的线程(独占线程) 因为ReentrantLock是可重入锁,获取锁的线程可以再次进入
//如果条件成立 说明当前线程是独占锁的线程 要做些什么呢?这里是可重入的场景
else if (current == getExclusiveOwnerThread()) {
//获取当前线程的加锁状态 并累加
int nextc = c + acquires;
//这里是越界判断,当重入的深度很深超过int值的Max之后,再次+1会变成负数
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//更新加锁状态
setState(nextc);
//true ->当前线程尝试获取锁成功
return true;
}
//false 尝试获取锁失败的场景
//第一种情况是:CAS加锁失败 且当前线程前面有等待的线程
//第二种情况是:state > 0 且当前线程不是占用锁的线程
return false;
}
4.addWaiter将当前线程添加到阻塞队列的方法
//位于AQS下:将当前线程添加到阻塞队列的方法
//最终返回包装当前线程的node
private Node addWaiter(Node mode) {
//构建Node 把当前线程封装到Node中 mode:Node节点的模式,例如Node.EXCLUSIVE 当前节点是独占模式
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//线程快速入队的方式
//先获取队尾节点 保存到pred
Node pred = tail;
//这里判断队尾节点是否为空 条件成立的话 说明队列中已经有node了
if (pred != null) {
//令当前节点node的前驱指向pred 1.第一次绑定 将当前节点的node的prev指向pred
node.prev = pred; 2.通过CAS更新队尾tail
//基于CAS更新队尾tail 3.第二次绑定 将pred的next指向node 完成双向绑定
if (compareAndSetTail(pred, node)) {
//tail更新成功后:设置pred的next指向node 这时完成双向绑定
pred.next = node;
//返回node
return node;
}
}
//线程完整入队方式(自旋入队)
//执行到这里有以下两种情况
//1.tail == null 当前队列是空队列
//2.cas设置当前newNode为tail时失败了,被其他线程抢先一步
//这里是自旋入队的逻辑 只有入队成功才结束自旋
enq(node);
//返回node
return node;
}
5.enq当前线程完整入队的方法(自旋入队)
//位于AQS中将当前线程完整入队的方法
private Node enq(final Node node) {
//这里是自旋的逻辑 只有封装当前线程node入队成功 才会跳出循环
for (;;) {
//先获取到尾结点
Node t = tail;
//第一种情况:空队列 -> 当前线程是第一个抢占锁失败的线程
//当前持有锁的线程(tryAcquire方法直接获取到锁的线程,在该方法逻辑中,并没有将持有锁的线程入队)
//而按理说阻塞队列的head结点就应该是当前持有锁的线程才对,并没有设置过任何node,
//所以作为该线程的第一个后驱next,需要给持锁线程补一个node结点并设置为阻塞队列的head
//head结点任何时候 都代表当前占用锁的线程
if (t == null) { // Must initialize 需要初始化一个head
//如果compareAndSetHead条件成立:说明当前线程给当前持有锁的线程,补充head操作成功
if (compareAndSetHead(new Node()))
//表示队列中只有一个元素,这里就表名当前持有锁的线程被放入阻塞队列且为head了
tail = head;
//注意:这里并没有返回 还会继续自旋 下次再进入循环时阻塞队列中已经有节点了,且head为持有锁的节点
} else {
//队列中已经有元素了 这里是在队尾追加node的逻辑
//如何入队列呢?和addWaiter方法入队逻辑一样
//1.找到队尾节点t
//2.将newNode的prev指向t
//3.CAS更新tail为neeNode
//4.更新t的next指向newNode
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//注意:入队成功 一定要return终止无限for循环
//返回此时的队尾节点
return t;
}
}
}
}
6.acquireQueued真正去竞争资源的方法
acquireQueued需要做什么呢?
- 1.当前节点如果没有被park挂起,则 ===> 挂起当前线程。
- 2.线程唤醒后 ===> 需要做一些线程唤醒之后的逻辑。
//位于AQS中:真正去竞争资源的方法
//参数final Node node:封装当前线程的node,通过上述的分析我们知道该node已经入队成功了
//参数arg :当前线程抢占资源成功后,更新state值是需要用到
//返回true:表示挂起过程中线程中断唤醒过,返回false 表示未被中断唤醒过
final boolean acquireQueued(final Node node, int arg) {
// true:表示当前线程抢占锁不成功 失败了 需要执行出队逻辑
// false:表示当前线程抢占锁成功
boolean failed = true;
try {
// 当前线程是否被中断
boolean interrupted = false;
// 自旋操作
for (;;) {
//什么情况下会执行到这里
//1.进入for循环时,在线程尚未被park前会执行
//2.线程park后,被唤醒之后也会执行
//获取当前节点的前驱节点
final Node p = node.predecessor();
//p == head条件成立时:说明当前node为head节点的后继节点 head.next指向node 有权利去争夺锁
//tryAcquire(arg) 尝试去获取锁,如果条件成立,说明head对应的线程已经释放了锁,而作为head的后继节点的线程,刚好可以获取锁
//tryAcquire 如果条件不成立:说明head对应的线程尚未释放锁,而作为head的后继节点的线程,这时候仍需要继续park挂起
if (p == head && tryAcquire(arg)) {
//运行到这里 表示两个条件都满足 拿到锁
//设置封装当前线程的节点为head节点(head无论什么时候都是持锁线程的节点)
setHead(node);
//到这里来我们知道p节点为传入参数node节点的前驱节点 p节点为head 进行了setHead操作后 此时的head是node节点 p.next设置为null 是指P已经是垃圾了需要回收 没有引用指向P
p.next = null; // help GC
//当前线程抢占锁没有失败 failed = false
failed = false;
//此时这个逻辑是当前node抢占到了锁 未被中断过就返回false
return interrupted;
}
//shouldParkAfterFailedAcquire:判断当前线程获取锁资源失败后,是否需要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
//parkAndCheckInterrupt:挂起当前线程,并在该线程唤醒之后,返回当前线程的interrupted中断标记
//1.正常唤醒:其他线程调用unpark方法,唤醒该线程
//2.其他线程给当前挂起的线程一个中断信号(中断挂起)
pakAndCheckInterrupt())
//interrupted=true表示当前node对应的线程是被中断信号唤醒的
interrupted = true;
}
} finally {
//failed为true时 表示当前线程抢占锁不成功
if (failed)
//取消node节点的线程资源竞争
cancelAcquire(node);
}
}
7.shouldParkAfterFailedAcquire方法
//位于AQS中:判断当前线程获取锁资源失败后,是否需要挂起
//true: 需要挂起 false:不需要挂起
//参数1:Node pred 当前线程的前驱节点
//参数2:Node node 封装当前线程的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的waitStatus
//0:默认状态 | -1:Singnal状态(表示当前节点释放锁后会唤醒它的第一个后继节点)
//waitStatus>0 表示当前节点是CANCELED状态
int ws = pred.waitStatus;
//如果条件成立 则表示前驱节点是可以唤醒当前线程节点的节点
if (ws == Node.SIGNAL)
//返回true后,在acquireQueue方法中会继续调用parkAndCheckInterrupt方法去park当前线程节点
//注意:一般情况下,第一次来到shouldParkAfterFailedAcquire方法中时,ws不会是-1
return true;
//如果ws>0成立 表示当前节点是CANCALED状态
if (ws > 0) {
//该循环是一个找pred.waitStatus>0 的前驱节点的过程
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//找到符合条件的前驱节点后,令其下一个节点为当前线程的node
//隐含着一种操作 即CANCELED状态的节点会被出队
pred.next = node;
} else {
//当前node前驱节点的状态就是0,即默认状态这种情况
//将当前线程node的前驱节点的状态,强制设置为SIGNAL,表示该节点释放锁后会唤醒它的第一个后继节点。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//位于AQS中 挂起当前线程节点
private final boolean parkAndCheckInterrupt() {
//线程挂起
LockSupport.park(this);
return Thread.interrupted();
}