AQS源码探究_04 成员方法解析(释放锁响应中断出队逻辑)
Posted 兴趣使然の草帽路飞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AQS源码探究_04 成员方法解析(释放锁响应中断出队逻辑)相关的知识,希望对你有一定的参考价值。
AQS成员方法解析(释放锁逻辑)
1. unlock释放锁方法
// 位于RentrantLock中:释放锁的方法
public void unlock() {
// 释放锁
sync.release(1);
}
// 位于AQS的静态内部类Sync中:真正释放锁的方法
// RentrantLock.unlock() -> sync.release()
public final boolean release(int arg) {
// tryRelease尝试释放锁:
// true: 当前线程已经完全释放锁
// false:当前线程尚未完全释放锁
if (tryRelease(arg)) {
// head 什么情况下会被创建出来?
// 当持锁线程未释放线程,且持锁期间有其他线程想要获取锁时,其他线程发现无法获取锁,
// 且此时阻塞队列是空队列,此时后续线程会为当前持锁线程构建出一个head节点(将持锁线程封装入head)
// 然后后续线程会追加到head节点的后面(成为head的后驱)
Node h = head;
// 条件1:h != null成立,说明队列中的head节点已经初始化过了,ReentrantLock在试用期间,发生过多线程竞争了~
// 条件2:h.waitStatus != 0 成立,说明当前head后面一定插入过node节点~
if (h != null && h.waitStatus != 0)
// 唤醒后驱节点~
unparkSuccessor(h);
return true;
}
return false;
}
2. tryRelease尝试释放锁的方法
// 位于AQS的静态内部类Sync中:尝试释放锁方法
// true: 当前线程已经完全释放锁 | false:当前线程尚未完全释放锁
protected final boolean tryRelease(int releases) {
// state状态变量的值相减
int c = getState() - releases;
// 如果条件成立:说明当前线程并未持锁 -> 直接抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 当前线程持有锁
// 当前线程是否已经完全释放锁,默认初始值false
boolean free = false;
// c == 0条件成立时:说明当前线程已经达到完全释放锁的条件
if (c == 0) {
// free = true 当前线程已经完全释放锁
free = true;
// 更新当前持锁线程为null
setExclusiveOwnerThread(null);
}
// 更新state(基于CAS)
setState(c);
// 返回free
return free;
}
3. unparkSuccessor唤醒后驱节点线程的方法
// 位于AQS中:唤醒后驱节点线程的方法
private void unparkSuccessor(Node node) {
// 获取当前node节点的waitStatus状态
int ws = node.waitStatus;
if (ws < 0)// -1:SIGNAL
// 改成0的原因:因为当前节点已经完成唤醒后驱节点线程的任务了~
compareAndSetWaitStatus(node, ws, 0);
// s是当前节点的第一个后驱节点
Node s = node.next;
// 条件1:s == null
// s 什么时候为null?
// 1.当前节点就是tail节点时,s==null
// 2.当前节点入队未完成时(1.设置新节点的prev指向pred 2.CAS设置新节点为tail 3.(未完成)pred.next -> 新节点)
// 需要找到可以被唤醒的节点...
// 条件2:s.waitStatus > 0 前提是 s == null
// 如果条件2成立,则说明当前node节点的后继节点是取消状态,需要找一个合适的可以被唤醒的节点...
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;
// 上面的循环,会找到一个离当前node最近的一个可以被唤醒的节点,该节点可能找不到,可能是null
}
// 如果找到合适的,且可以被唤醒的节点s,则将其挂起,如果没找到,则什么也不做~
if (s != null)
LockSupport.unpark(s.thread);
}
上一篇文章:AQS源码探究_03 成员方法解析(加锁、资源竞争逻辑) 和 本篇文章的前面部分,都是在介绍ReentrantLock的lock()
加锁方式,这种加锁方式是不可以被响应中断的,下面我们分析可以被响应中断的加锁方式lockInterruptibly()
:
扩展:AQS成员方法解析(响应中断加锁逻辑)
1. lockInterruptibly可以被响应中断的加锁方法
// 位于ReentrantLock中:可以被响应中断的加锁方法
public void lockInterruptibly() throws InterruptedException {
// 可以被响应中断的方式去竞争资源~
sync.acquireInterruptibly(1);
}
// 位于AQS的Sync静态内部类中:竞争资源的方法(可以被响应中断)
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 如果当前线程已经是有中断标记interrupted为true了,则直接抛出中断异常~
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取锁
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
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())
throw new InterruptedException();
}
} finally {
if (failed)
// 我们主要来分析下cancelAcquire这个方法: 取消指定node参与竞争。
cancelAcquire(node);
}
2. cancelAcquire取消指定node参与竞争的方法
// 位于AQS下
/**
* 取消指定node参与竞争。
*/
private void cancelAcquire(Node node) {
//空判断..
if (node == null)
return;
//因为已经取消排队了..所以node内部关联的当前线程,置为Null就好了。。
node.thread = null;
//获取当前取消排队node的前驱。
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//拿到前驱的后继节点。
//1.当前node
//2.可能也是 ws > 0 的节点。
Node predNext = pred.next;
//将当前node状态设置为 取消状态 1
node.waitStatus = Node.CANCELLED;
/**
* 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:
* 1.当前node是队尾 tail -> node
* 2.当前node 不是 head.next 节点,也不是 tail
* 3.当前node 是 head.next节点。
*/
//条件一:node == tail 成立:当前node是队尾 tail -> node
//条件二:compareAndSetTail(node, pred) 成功的话,说明修改tail完成。
if (node == tail && compareAndSetTail(node, pred)) {
//修改pred.next -> null. 完成node出队。
compareAndSetNext(pred, predNext, null);
} else {
//保存节点 状态..
int ws;
//第二种情况:当前node 不是 head.next 节点,也不是 tail
//条件一:pred != head 成立, 说明当前node 不是 head.next 节点,也不是 tail
//条件二: ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
//条件2.1:(ws = pred.waitStatus) == Node.SIGNAL 成立:说明node的前驱状态是 Signal 状态 不成立:前驱状态可能是
// 极端情况下:前驱也取消排队了..
//条件2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
// 假设前驱状态是 <= 0 则设置前驱状态为 Signal状态..表示要唤醒后继节点。
//if里面做的事情,就是让pred.next -> node.next ,所以需要保证pred节点状态为 Signal状态。
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//情况2:当前node 不是 head.next 节点,也不是 tail
//出队:pred.next -> node.next 节点后,当node.next节点 被唤醒后
//调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
//完成真正出队。
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//当前node 是 head.next节点。 更迷了...
//类似情况2,后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
//队列的第三个节点 会 直接 与 head 建立 双重指向的关系:
//head.next -> 第三个node 中间就是被出队的head.next 第三个node.prev -> head
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
3. parkAndCheckInterruptpark当前线程方法
//AQS#parkAndCheckInterrupt
//park当前线程 将当前线程 挂起,唤醒后返回当前线程是否为中断信号唤醒。
private final boolean parkAndCheckInterrupt() {
// 挂起当前线程
LockSupport.park(this);
// 返回当前线程的中断标识
return Thread.interrupted();
}
小结
下面总结一下前面几篇文章的主要内容:
(1)AQS是Java中几乎所有锁和同步器的一个基础框架,这里说的是“几乎”,因为有极个别确实没有通过AQS来实现;
(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;
(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;
(4)基于AQS自己动手写一个锁非常简单,只需要实现AQS的几个方法即可。
以上是关于AQS源码探究_04 成员方法解析(释放锁响应中断出队逻辑)的主要内容,如果未能解决你的问题,请参考以下文章
Java小白进阶系列——Java锁框架AQS源码分析目录大纲
Java小白进阶系列——Java锁框架AQS源码分析目录大纲