共计 13133 个字符,预计需要花费 33 分钟才能阅读完成。
前言
AbstractQueuedSynchronizer 形象队列同步器,简称 AQS。是在 JUC 包上面一个十分重要的根底组件,JUC 包上面的并发锁
ReentrantLock
CountDownLatch
等都是基于 AQS 实现的。所以想进一步钻研锁的底层原理,十分有必要先理解 AQS 的原理。公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生存中的见闻感悟。欢送大佬来领导!
介绍
先看下 AQS 的类图构造,以及源码正文,有肯定的大略理解之后再从源码动手,一步一步钻研它的底层原理。
” 源码正文
提供了实现阻塞锁和相干同步器依附先入先出(FIFO)期待队列(信号量,事件等)的框架。此类中设计了一个对大多数基于 AQS 的同步器有用的原子变量来示意状态(state)。子类必须定义 protected 办法来批改这个 state,并且定义 state 值在对象中的具体含意是 acquired 或 released。思考到这些,在这个类中的其余办法能够实现所有排队和阻塞机制。子类能够放弃其余状态字段,但只能应用办法 getState、setState 和 compareAndSetState 以原子形式更新 state。
子类应被定义为用于实现其关闭类的同步性能的非公共外部辅助类。类 AbstractQueuedSynchronizer 没有实现任何同步接口。相同,它定义了一些办法,如 acquireInterruptibly 能够通过具体的锁和相干同步器来调用适当履行其公共办法。
此类反对独占模式和共享模式。在独占模式下,其余线程不能获取胜利,共享模式下能够(但不肯定)获取胜利。此类不“了解”,在机械意义上这些不同的是,当共享模式获取胜利,则下一个期待的线程(如果存在)也必须确定它是否可能获取。线程在不同模式下的期待共享雷同的 FIFO 队列。通常状况下,实现子类只反对其中一种模式,但同时应用两种模式也能够,例如 ReadWriteLock。仅共享模式不须要定义反对未应用的模式的办法的子类。
这个类中定义了嵌套类 AbstractQueuedSynchronizer.ConditionObject,可用于作为一个 Condition 由子类实现,并应用 isHeldExclusively 办法阐明以后线程是否以独占形式进行,release()、getState() acquire() 办法用于操作 state 原子变量。
此类提供检查和监督外部队列的办法,以及相似办法的条件对象。依据须要进应用以用于它们的同步机制。
要应用这个类用作同步的根底上,须要从新定义以下办法,如应用,通过检查和或批改 getState、setState 或 compareAndSetState 办法:
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively
“
通过下面的正文能够得出大略的印象:
- 外部依附先入先出(FIFO)期待队列。
- 存在 state 示意状态信息。state 值只能用 getState、setState 和 compareAndSetState 办法以原子形式更新。
- 反对独占模式和共享模式,但具体须要子类实现具体反对哪种模式。
- 嵌套 AbstractQueuedSynchronizer.ConditionObject 能够作为 Condition 由子类实现。
- 子类须要从新定义 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 办法。
队列节点 Node
Node 节点,蕴含以下元素:
元素 | 含意 |
---|---|
prev | 上一个节点 |
next | 下一个节点 |
thread | 持有线程 |
waitStatus | 节点状态 |
nextWaiter | 下一个处于 CONDITION 状态的节点 |
组合成期待队列则如下:
上面是期待队列节点的 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;
// 批示下一次 acquireShared 应该无条件流传
static final int PROPAGATE = -3;
/**
* 状态字段,仅应用以下值
* SIGNAL -1:以后节点开释或者勾销时,必须 unpark 他的后续节点。* CANCELLED 1:因为超时(timeout)或中断(interrupt),该节点被勾销。节点永远不会来到此状态。特地是,具备勾销节点的线程永远不会再次阻塞。* CONDITION -2:该节点目前在条件队列。但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0。* PROPAGATE -3:releaseShared 应该被流传到其余节点。* 0:都不是
* 值以数字示意以简化应用,大多数时候能够查看符号(是否大于 0)以简化应用
*/
volatile int waitStatus;
// 上一个节点
volatile Node prev;
// 下一个节点
volatile Node next;
// 节点持有线程
volatile Thread thread;
// 链接下一个期待条件节点,或非凡值共享
Node nextWaiter;
// 节点是否处于 共享状态 是,返回 true
final boolean isShared() {return nextWaiter == SHARED;}
// 返回前一个节点,应用时 前一个节点不能为空
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
在 Node 节点中须要重点关注 waitStatus
- 默认状态为 0;
- waitStatus > 0 (CANCELLED 1) 阐明该节点超时或者中断了,须要从队列中移除;
- waitStatus = -1 SIGNAL 以后线程的前一个节点的状态为 SIGNAL,则以后线程须要阻塞(unpark);
- waitStatus = -2 CONDITION -2:该节点目前在条件队列;
- waitStatus = -3 PROPAGATE -3:releaseShared 应该被流传到其余节点,在共享锁模式下应用。
理解完 Node 的构造之后,再理解下 AQS 构造,并从罕用办法动手,逐渐理解具体实现逻辑。
AbstractQueuedSynchronizer
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// 期待队列的头,提早初始化。除了初始化,它是仅经由办法 setHead 批改。留神:如果头存在,其 waitStatus 保障不会是 CANCELLED 状态
private transient volatile Node head;
// 期待队列的尾部,提早初始化。仅在批改通过办法 ENQ 增加新节点期待。private transient volatile Node tail;
// 同步状态
private volatile int state;
// 获取状态
protected final int getState() {return state;}
// 设置状态值
protected final void setState(int newState) {state = newState;}
// 原子更新状态值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
在 AQS 中主要参数为:
参数 | 含意 |
---|---|
head | 期待队列头 |
tail | 期待队列尾 |
state | 同步状态 |
通过正文理解到,在 AQS 里次要分为两种操作模式,别离是:独占模式、共享模式,上面别离从两个不同的角度去剖析源码。
操作 | 含意 |
---|---|
acquire | 以独占模式获取,疏忽中断。通过调用至多一次施行 tryAcquire,在胜利时返回。否则,线程排队,可能反复查封和解封,调用 tryAcquire 直到胜利为止。这种办法能够用来实现办法 Lock.lock。 |
release | 以独占模式开释。通过畅通一个或多个线程,如果实现 tryRelease 返回 true。这种办法能够用来实现办法 Lock.unlock。 |
acquireShared | 获取在共享模式下,疏忽中断。通过至多一次第一调用实现 tryAcquireShared,在胜利时返回。否则,线程排队,可能反复查封和解封,调用 tryAcquireShared 直到胜利为止。 |
releaseShared | 以共享模式开释。通过畅通一个或多个线程,如果实现 tryReleaseShared 返回 true。 |
无论是共享模式还是独占模式在这外面都会用到 addWaiter 办法,将以后线程及模式创立排队节点。
独占模式
获取独占资源 acquire
public final void acquire(int arg) {
// tryAcquire 尝试获取 state,获取失败则会退出到队列
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();}
在独占模式下会尝试获取 state,当获取失败时会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。
- tryAcquire(arg),尝试获取 state 这块由子类本人实现,不同的子类逻辑不同,这块在介绍子类代码时会阐明。
- 获取 state 失败后,会进行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这块代码能够拆分为两块:addWaiter(Node.EXCLUSIVE),acquireQueued(node, arg)。
- addWaiter(Node.EXCLUSIVE) 返回的是以后新创建的节点。
- acquireQueued(node, arg) 线程获取锁失败,应用 addWaiter(Node.EXCLUSIVE) 放入期待队列,而 acquireQueued(node, arg) 应用循环,一直的为队列中的节点去尝试获取资源,直到获取胜利或者被中断。
总结获取资源次要分为三步:
- 尝试获取资源
- 入队列
- 出队列
尝试获取资源 tryAcquire(arg)
,由子类实现,那上面则着手别离剖析 入队列
、 出队列
。
入队列:addWaiter(Node.EXCLUSIVE)
应用 addWaiter(Node.EXCLUSIVE)
办法将节点插入到队列中,步骤如下:
- 依据传入的模式创立节点
- 判断尾节点是否存在,不存在则须要应用
enq(node)
办法初始化节点,存在则间接尝试
插入尾部。 尝试
插入尾部时应用 CAS 插入,避免并发状况,如果插入失败,会调用enq(node)
自旋直到插入。
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);
// 定位到队列开端的 node
Node pred = tail;
if (pred != null) {
// 新节点的上一个节点 指向尾节点
node.prev = pred;
// 应用 CAS 设置尾节点,tail 如果等于 pred 则更新为 node
if (compareAndSetTail(pred, node)) {
// 更新胜利则将 pred 的下一个节点指向 node
pred.next = node;
return node;
}
}
// 尾节点没有初始化,或竞争失败,自旋
enq(node);
return node;
}
/**
* tailOffset 也就是成员变量 tail 的值
* 此处相当于:比拟 tail 的值和 expect 的值是否相等,相等则更新为 update
*/
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private Node enq(final Node node) {for (;;) {
Node t = tail;
// 尾节点为空 须要初始化头节点,此时头尾节点是一个
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 不为空 循环赋值
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
看完代码和正文必定还是有点含糊,当初用图一步一步进行阐明。
因为依据 初始尾节点是否为空
分为两种状况,这里应用两幅图:
- 第一幅为第一次增加节点,此时 head 会提早初始化;
- 第二幅图为曾经存在队列,进行插入节点;
- 留神看代码,enq 办法返回的是
之前的尾节点
; - addWaiter 办法 返回的是
以后插入的新创建的节点
。
节点增加到队列之后,返回以后节点,而下一步则须要调用办法 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
一直的去获取资源。
出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
办法会通过循环不断尝试获取拿到资源,直到胜利。代码如下:
final boolean acquireQueued(final Node node, int arg) {
// 是否拿到资源
boolean failed = true;
try {
// 中断状态
boolean interrupted = false;
// 有限循环
for (;;) {
// 以后节点之前的节点
final Node p = node.predecessor();
// 前一个节点是头节点,阐明以后节点是 头节点的 next 即实在的第一个数据节点(因为 head 是虚构节点)// 而后再尝试获取资源
if (p == head && tryAcquire(arg)) {
// 获取胜利之后 将头指针指向以后节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// p 不是头节点,或者 头节点未能获取到资源(非偏心状况下被别的节点抢占)// 判断 node 是否要被阻塞,if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {if (failed)
cancelAcquire(node);
}
}
- 一直获取本节点的上一个节点是否为 head,因为 head 是虚构节点,如果以后节点的上一个节点是 head 节点,则以后节点为
第一个数据节点
; - 第一个数据节点一直的去获取资源,获取胜利,则将 head 指向以后节点;
- 以后节点不是头节点,或者
tryAcquire(arg)
失败(失败可能是非偏心锁)。这时候须要判断前一个节点状态决定以后节点是否要被阻塞
(前一个节点状态是否为 SIGNAL)。
/**
* 依据上一个节点的状态,判断以后线程是否应该被阻塞
* SIGNAL -1:以后节点开释或者勾销时,必须 unpark 他的后续节点。* CANCELLED 1:因为超时(timeout)或中断(interrupt),该节点被勾销。节点永远不会来到此状态。特地是,具备勾销节点的线程永远不会再次阻塞。* CONDITION -2:该节点目前在条件队列。但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0。* PROPAGATE -3:releaseShared 应该被流传到其余节点。* 0:都不是
*
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前一个节点的期待状态
int ws = pred.waitStatus;
// 前一个节点须要 unpark 后续节点
if (ws == Node.SIGNAL)
return true;
// 以后节点处于勾销状态
if (ws > 0) {
do {
// 将勾销的节点从队列中移除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置前一个节点为 SIGNAL 状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在 shouldParkAfterFailedAcquire
办法中,会判断前一个节点的状态,同时勾销在队列中以后节点后面有效的节点。
再持续浏览 出队列 acquireQueued 办法,发现有一个 finally 会判断状态后执行 cancelAcquire(node);
,也就是下面流程图中上面的红色方块。
cancelAcquire(Node node)
final boolean acquireQueued(final Node node, int arg) {
// 是否拿到资源
boolean failed = true;
try {
// 省略
// 在 finally 会将以后节点置为勾销状态
} finally {if (failed)
cancelAcquire(node);
}
}
private void cancelAcquire(Node node) {
// 节点不存在 间接返回
if (node == null)
return;
// 勾销节点关联线程
node.thread = null;
// 跳过曾经勾销的节点,获取以后节点之前的无效节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取以后节点之前的无效节点的下一个节点
Node predNext = pred.next;
// 以后节点设置为勾销
node.waitStatus = Node.CANCELLED;
// 以后节点如果是尾节点,则将最初一个无效节点设置为尾节点,并将 predNext 设置为空
if (node == tail && compareAndSetTail(node, pred)) {compareAndSetNext(pred, predNext, null);
} else {
int ws;
// pred 不是头节点(node 的上一个无效节点 不是 head) &&(pred 的状态是 SIGNAL || pred 的状态设置为 SIGNAL 胜利)&& pred 的绑定线程不为空
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 以后节点的后继节点
Node next = node.next;
// 后继节点不为空 且 状态无效 将 pred 的 后继节点设置为 以后节点的后继节点
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// node 的上一个无效节点 是 head,或者其余状况 唤醒以后节点的下一个无效节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
private void unparkSuccessor(Node node) {
// 判断以后节点状态
int ws = node.waitStatus;
if (ws < 0)
// 将节点状态更新为 0
compareAndSetWaitStatus(node, ws, 0);
// 下一个节点,个别是下一个节点应该就是须要唤醒的节点,即颁发证书。Node s = node.next;
// 大于 0 CANCELLED:线程已勾销
// 然而有可能 后继节点 为空或者被勾销了。if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点开始遍历,直到定位到 t.waitStatus <= 0 的节点
// 定位到后并不会进行,会继续执行,相当于找到最开始的那个须要唤醒的节点
// t.waitStatus <= 0:SIGNAL(-1 后续线程须要开释)// CONDITION(-2 线程正在期待条件)// PROPAGATE(-3 releaseShared 应该被流传到其余节点)for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 定位到须要唤醒的节点后 进行 unpark
if (s != null)
LockSupport.unpark(s.thread);
}
流程剖析:
- 找到以后节点的前一个非有效节点 pred;
- 以后节点如果是尾节点,则将最初一个无效节点设置为尾节点,并将 predNext 设置为空;
- pred 不是头节点 &&(pred 的状态是 SIGNAL || pred 的状态设置为 SIGNAL 胜利)&& pred 的绑定线程不为空;
- 其余状况。
上面别离画图:
Q: 通过图能够看进去,只操作了 next 指针,然而没有操作 prev 指针,这是为什么呢?
A: 在 出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
办法中,shouldParkAfterFailedAcquire
办法会判断前一个节点的状态,同时勾销在队列中以后节点后面有效的节点。这时候会移除之前的有效节点,此处也是为了避免指向一个曾经被移除的节点。同时保障 prev 的稳固,有利于从 tail 开始遍历列表,这块在 unparkSuccessor(node);
中也能够看到是从后往前表里列表。
Q: unparkSuccessor(Node node) 为什么从后往前遍历?
A:
在 addWaiter(Node.EXCLUSIVE)
插入新节点时,应用的是 尾插法
,看红框局部,此时有可能还未指向 next。
Q: node.next = node; 这块导致 head 不是指向最新节点,链表不就断了么?
A: acquireQueued 办法介绍中,外面有个循环,会一直尝试获取资源,胜利之后会设置为 head。并且在 shouldParkAfterFailedAcquire 中也会革除以后节点前的有效节点。
开释独占资源 release
public final boolean release(int arg) {if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
以独占模式开释。通过开释一个或多个线程,如果实现 tryRelease 返回 true。这种办法能够用来实现办法 Lock.unlock。
- tryRelease(arg) 操作开释资源,同样是由子类实现,前面介绍子类时会进行阐明。返回 true 阐明资源当初曾经没有线程持有了,其余节点能够尝试获取;
- 开释胜利,且 head != null && h.waitStatus != 0, 会继续执行 unparkSuccessor(h);
- 这块会看到 只有 tryRelease(arg) 操作开释资源胜利,前面无论执行是否胜利,都会返回 true,unparkSuccessor(h) 相当于只是附加操作。
共享模式
获取共享资源 acquireShared
public final void acquireShared(int arg) {
// 小于 0 示意获取资源失败
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 增加到节点 此处是共享节点
final Node node = addWaiter(Node.SHARED);
// 依据是否拿到资源 判断是否须要勾销
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 返回前一个节点
final Node p = node.predecessor();
if (p == head) {
// 再次尝试获取共享资源
int r = tryAcquireShared(arg);
// 示意获取胜利
if (r >= 0) {
// 设置以后节点为头节点 并尝试唤醒后续节点
setHeadAndPropagate(node, r);
// 开释头节点 GC 会回收
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {if (failed)
cancelAcquire(node);
}
}
- tryAcquireShared(arg),尝试获取资源,这块由子类实现;
-
返回值分为 3 种:
- 小于 0: 示意失败;
- 等于 0: 示意共享模式获取资源胜利,但后续的节点不能以共享模式获取胜利;
- 大于 0: 示意共享模式获取资源胜利,后续节点在共享模式获取也可能会胜利,在这种状况下,后续期待线程必须查看可用性。
- 在失败后会应用
doAcquireShared(arg);
一直获取资源; final Node node = addWaiter(Node.SHARED);
同样会创立节点;- 在循环中一直判断前一个节点如果是 head,则尝试获取资源;
- 在共享模式下获取到资源后会应用
setHeadAndPropagate(node, r);
设置头节点,同时唤醒后续节点。
设置头节点,并流传唤醒后续节点
// node 是以后节点
// propagate 是 前一步 tryAcquireShared 的返回值 进来时 >=0
// 大于 0: 示意共享模式获取资源胜利,后续节点在共享模式获取也可能会胜利,在这种状况下,后续期待线程必须查看可用性。private void setHeadAndPropagate(Node node, int propagate) {
// 记录下以后头节点
Node h = head; // Record old head for check below
// 设置传入 node 为头节点
setHead(node);
// 判断条件,唤醒后续节点
// propagate > 0 有后续资源
// h == null 旧的头节点 因为后面 addWaiter,必定不会为空,应该是避免 h.waitStatus < 0 空指针的写法
// (h = head) == null 以后的 头节点,再判断状态
// waitStatus < 0 后续节点就须要被唤醒
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 后续节点为共享,则须要唤醒
if (s == null || s.isShared())
doReleaseShared();}
}
doReleaseShared() 开释共享资源
private void doReleaseShared() {
// 循环
for (;;) {
// 从头开始
Node h = head;
// 判断队列是否为空,就是刚初始化
if (h != null && h != tail) {
int ws = h.waitStatus;
// SIGNAL(-1 后续线程须要开释)if (ws == Node.SIGNAL) {
// 将期待状态更新为 0 如果失败,会循环
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后续节点,同时将以后节点设置为 勾销
unparkSuccessor(h);
}
// 如果状态是 0 则会更新状态为 PROPAGATE
// PROPAGATE(-3 releaseShared 应该被流传到其余节点)else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 判断头节点有没有变动,有变动 是因为竞争,别的线程获取到了锁,会持续循环
// 没有变动间接完结
if (h == head) // loop if head changed
break;
}
}
- 从头节点开始进行,如果 h != null && h != tail 阐明队列不是空或者刚初始化;
- 节点状态为 SIGNAL(-1)阐明后续线程须要开释;
- 会更改以后节点状态,胜利后唤醒后续节点,失败则持续循环;
- 节点状态如果是 0 则更新为 PROPAGATE,会将状态流传。
开释共享资源 releaseShared
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {
// 开释共享资源
doReleaseShared();
return true;
}
return false;
}
以共享模式开释。通过开释一个或多个线程,如果实现 tryReleaseShared 返回 true。
总结
Q: AQS 到底是什么?
A: AQS 外部提供了一个先入先出(FIFO)双向期待队列,外部依附 Node 实现,并提供了在 独占模式
和共享模式
下的出入队列的公共办法。而对于状态信息 state 的定义是由子类实现。tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared 等尝试获取资源操作都是由子类进行定义和实现的。而 AQS 中提供了子类获取资源之后的相干操作,包含节点 Node 的出入队列,自旋获取资源等等。
Q: AQS 获取资源失败后会如何操作?
A: 线程获取资源失败后,会放到期待队列中,在队列中会一直尝试获取资源(自旋),阐明线程只是进入期待状态,前面还是能够再次获取资源的。
Q: AQS 期待队列的数据结构是什么?
A: CLH 变体的先入先出(FIFO)双向期待队列。(CLH 锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。是一种基于链表的可扩大、高性能、偏心的自旋锁,申请线程仅仅在本地变量上自旋,它一直轮询前驱的状态,如果发现前驱开释了锁就完结自旋。)
Q: AQS 期待队列中的节点如何获取获取和开释资源的?
A: 能够看下 独占模式
中的讲述过程,通过代码梳理。
本文别离从 独占模式
和 共享模式
介绍的 AQS 根本逻辑,并通过源码和作图了解基本思路。然而并没有对须要子类实现的业务逻辑做介绍。这块会在前面介绍 ReentrantLock
、CountDownLatch
等子类的时候做介绍。