【Java 并发编程】AQS
前言
Java中的大部分同步类,如 Lock、Semaphore、ReentrantLock 等,都是基于 AbstractQueuedSynchronizer(简称为AQS)实现的。
AQS 是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。AQS 的框架如下图所示:
总的来说,AQS 框架共分为五层,自上而下由浅入深,从 AQS 对外暴露的 API 到底层基础数据。
CLH 锁
CLH 锁是由 Craig、Landin 和 Hagersten 的发明,因此命名为 CLH 锁。CLH 是单向链表,AQS 中的队列是 CLH 变体的虚拟双向队列(FIFO),AQS 是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。
AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个节点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
CLH 队列结构如下图所示:
AQS 框架
AQS 核心思想
AQS 核心思想是:
-
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
-
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。
这个机制 AQS 是基于 CLH 锁实现的。
AQS 的核心原理图:
AQS 的同步状态
AbstractQueuedSynchronizer 使用了一个 int 成员变量 state 表示同步状态,通过内置的 FIFO 线程等待/等待队列 来完成获取资源线程的排队工作。并且,状态的获取和修改都是通过 final 修饰的,在子类中无法被重写。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
private volatile int state; // The synchronization state.
// 返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
// 原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
...
}
我们可以通过修改 state 字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程):
对于我们自定义的同步工具,需要自定义获取同步状态和释放状态的方式,也就是 AQS 架构图中的第一层:API层。
AQS 对资源的共享方式
AQS 中线程的两种锁的模式:
-
独占(Exclusive):只有一个线程能执行,如,ReentrantLock。
独占方式,又分为公平锁和非公平锁:
-
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁;
-
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。
-
-
共享(Shared):多个线程可同时执行,如,Semaphore、CountDownLatch。
ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 也就是读写锁允许多个线程同时对某一资源进行读。
AQS 的重要方法
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在顶层实现好了。
对于自定义同步器,需要实现以下几种方法:
方法名 | 描述 |
---|---|
protected boolean isHeldExclusively() | 该线程是否正在独占资源。只有用到 Condition 才需要去实现它。 |
protected boolean tryAcquire(int arg) | 独占方式。arg为获取锁的次数,尝试获取资源,成功则返回True,失败则返回False。 |
protected boolean tryRelease(int arg) | 独占方式。arg为释放锁的次数,尝试释放资源,成功则返回True,失败则返回False。 |
protected int tryAcquireShared(int arg) | 共享方式。arg为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 |
protected boolean tryReleaseShared(int arg) | 共享方式。arg为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待节点返回True,否则返回False。 |
一般来说,自定义同步器要么是独占方法,要么是共享方式,它们也只需实现:tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared 中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock。
AQS 的数据结构
Node
先来看下AQS中最基本的数据结构:Node,Node即为上面CLH变体队列中的节点。
// java.util.concurrent.locks.AbstractQueuedSynchronizer.Node@JDK 1.8
abstract static class Node {
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 当前节点在队列中的状态
volatile int waitStatus;
// 前驱指针
volatile Node prev;
// 后继指针
volatile Node next;
// 表示处于该节点的线程
volatile Thread thread;
// 指向下一个处于CONDITION状态的节点
Node nextWaiter;
...
}
其中,节点的状态 waitStatus 有下面几个枚举值:
枚举变量 | 枚举值 | 含义 |
---|---|---|
- | 0 | 一个新节点入队的默认状态,表示当前节点在 sync queue 中,等待着获取锁。 |
CANCELLED | 1 | 表示当前节点已取消调度。当 timeout 或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的节点将不会再变化。 |
CONDITION | -2 | 表示节点等待在 Condition 上,当其他线程调用了 Condition 的 signal() 方法后, CONDITION 状态的节点将从等待队列转移到同步队列中,等待获取同步锁。 |
PROPAGATE | -3 | 共享模式下,前继节点不仅会唤醒其后继节点,同时也可能会唤醒后继的后继节点。 |
SIGNAL | -1 | 表示后继节点在等待当前节点唤醒。后继节点入队时,会将前继节点的状态更新为 SIGNAL。 |
注意,负值表示节点处于有效等待状态,而正值表示节点已被取消,因此,源码中很多地方用 ws > 0
、ws < 0
来判断节点的状态是否正常。
ConditionObject
Condition
// java.util.concurrent.locks.Condition
public interface Condition {
// 等待,当前线程在接到信号或被中断之前一直处于等待状态
void await() throws InterruptedException;
// 等待,当前线程在接到信号之前一直处于等待状态,不响应中断
void awaitUninterruptibly();
//等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
void signal();
// 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
void signalAll();
}
ConditionObject
ConditionObject 实现了 Condition 接口,Condition 接口定义了条件操作规范
// java/util/concurrent/locks/AbstractQueuedSynchronizer.java
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public class ConditionObject implements Condition, java.io.Serializable {
// condition 队列的头节点
private transient Node firstWaiter;
// condition 队列的尾节点
private transient Node lastWaiter;
...
}
...
}
AQS 源码分析
AbstractQueuedSynchronizer 的源码如下所示:
// java/util/concurrent/locks/AbstractQueuedSynchronizer.java
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
// 头节点
private transient volatile Node head;
// 尾节点
private transient volatile Node tail;
// 状态
private volatile int state;
// 自旋时间
static final long spinForTimeoutThreshold = 1000L;
// Unsafe类实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// state内存偏移地址
private static final long stateOffset;
// head内存偏移地址
private static final long headOffset;
// state内存偏移地址
private static final long tailOffset;
// tail内存偏移地址
private static final long waitStatusOffset;
// next内存偏移地址
private static final long nextOffset;
// 静态初始化块
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
...
}
核心方法
获取资源:acquire
获取资源的入口是 acquire(int arg)
方法,arg 是要获取的资源个数,在独占模式下始终为 1。
acquire()
方法的源码如下:
// java/util/concurrent/locks/AbstractQueuedSynchronizer.java
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
}
在 AQS 源码中,tryAcquire 默认会抛出一个异常,即需要子类去重写此方法完成自己的逻辑。
acquire()
方法以独占模式获取(资源),忽略中断,即线程在 aquire()
过程中,中断此线程是无效的。
当一个线程调用 acquire()
时,会首先调用 tryAcquire()
方法,调用此方法的线程会试图在独占模式下获取资源,此方法应该查询是否允许它在独占模式下获取资源,如果允许,则获取它。
如果 tryAcquire()
失败,则调用 Node addWaiter(Node mode)
方法,addWaiter()
方法将把这个线程插入到等待队列中,其中,参数 Node 是需要独占模式插入的节点。
acquire()
的流程图:
addWaiter
addWaiter()
方法会在队列的尾部插入新的 Node 节点,但是需要注意的是由于 AQS 中会存在多个线程同时争夺资源的情况,因此肯定会出现多个线程同时插入节点的操作,在这里是通过 CAS 自旋的方式保证了操作的线程安全性。
addWaiter()
方法的源码如下:
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
private Node addWaiter(Node mode) {
// 新生成一个节点,默认为独占模式
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; // 保存尾节点
if (pred != null) { // 尾节点不为空,即已经被初始化
node.prev = pred; // 将 node 节点的prev域连接到尾节点
// 比较 pred 是否为尾节点,是则将尾节点设置为node
if (compareAndSetTail(pred, node)) {
pred.next = node; // 设置尾节点的 next 域为 node
return node; // 返回新生成的节点
}
}
// 尾节点为空(即还没有被初始化过),或者是 compareAndSetTail 操作失败,则入队列
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) { // 自旋确保节点能够成功入队列
Node t = tail; // 保存尾节点
if (t == null) { // 尾节点为空,即还没被初始化
if (compareAndSetHead(new Node())) // 头节点为空,并设置头节点为新生成的节点
tail = head; // 头节点与尾节点都指向同一个新生节点
} else { // 尾节点不为空,即已经被初始化过
node.prev = t; // 将 node 节点的 prev 域连接到尾节点
if (compareAndSetTail(t, node)) { // 比较节点 t 是否为尾节点,若是则将尾节点设置为 node
t.next = node; // 设置尾节点的 next 域为 node
return t; // 返回尾节点
}
}
}
}
...
}
addWaiter()
方法使用快速添加的方式往 sync queue 尾部添加节点,如果 sync queue 队列还没有初始化,则会调用 enq()
插入队列中,enq()
方法会使用自旋来确保节点的成功插入。
acquireQueue
如果线程获取资源(锁)失败,说明线程已经被添加到等待队列尾部了。acquireQueued()
方法可以对排队中的线程进行“获锁”操作。
总的来说,一个线程获取锁失败了,被放入等待队列,acquireQueued()
会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)。
acquireQueue()
方法的源码如下:
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
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)) { // 如果 p 是头节点,说明当前节点在真实数据队列的首部,就尝试获取锁(注意,头节点是虚节点)
setHead(node); // 获取锁成功,头指针移动到当前node
p.next = null; // help GC
failed = false; // 设置标志
return interrupted;
}
// 说明 p 为头节点且当前没有获取到锁(可能是非公平锁被抢占了)或者是 p 不为头节点,
// 这个时候就要判断当前 node 是否要被阻塞(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源。
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 将节点状态设置为 CANCELLED
}
}
// 靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 获取头节点的节点状态
if (ws == Node.SIGNAL) // 说明头节点处于唤醒状态
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true; // 可以进行 park 操作
if (ws > 0) { // 表示状态为:取消状态
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do { // 循环向前查找取消节点,把取消节点从队列中剔除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // 前置节点处于有效等待状态
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 设置前置节点等待状态为 SIGNAL
}
return false;
}
// 主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态。
private final boolean parkAndCheckInterrupt() {
// 在许可可用之前禁用当前线程,并且设置了 blocker
LockSupport.park(this); // 阻塞当前线程,直到 unpark 方法被调用或当前线程被中断,park 方法才会返回
return Thread.interrupted(); // 当前线程是否已被中断,并清除中断标记位
}
...
}
调用 acquireQueue()
方法,可以使 sync queue 中的节点在独占且忽略中断的模式下获取(资源)。其中,acquireQueued()
方法的流程图如下:
只有当该节点的前驱节点的状态为 SIGNAL 时,才可以对该节点所封装的线程进行 park()
操作;否则,将不能进行 park()
操作。parkAndCheckInterrupt()
方法里的逻辑是首先执行 park()
操作,即禁用当前线程,然后返回该线程是否已经被中断。其中,shouldParkAfterFailedAcquire()
方法的流程图如下:
可以看出,节点进入等待队列后,会调用 park()
使它进入阻塞状态的,只有头节点的线程是处于活跃状态的。
生成 CANCELLED 状态的节点:cancelAcquire
通过 cancelAcquire()
方法,可以会将 Node 的状态标记为 CANCELLED。其源码如下:
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
private void cancelAcquire(Node node) {
// 将无效节点过滤
if (node == null)
return;
// 设置该节点不关联任何线程,也就是虚节点
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
// 通过前驱节点,跳过取消状态的node
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取过滤后的前驱节点的后继节点
Node predNext = pred.next;
// 把当前node的状态设置为CANCELLED
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,将从后往前的第一个非取消状态的节点设置为尾节点
// 更新失败的话,则进入 else,如果更新成功,将 tail 的后继节点设置为 null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 如果当前节点不是 head 的后继节点,1:判断当前节点前驱节点的是否为SIGNAL,2:如果不是,则把前驱节点设置为SINGAL看是否成功
// 如果 1 和 2 中有一个为 true,再判断当前节点的线程是否为 null
// 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node); // 如果当前节点是head的后继节点,或者上述条件不满足,那就唤醒当前节点的后继节点
}
node.next = node; // help GC
}
}
...
}
cancelAcquire()
的具体流程如下:
-
获取当前节点的前驱节点,如果前驱节点的状态是 CANCELLED,那就一直往前遍历,找到第一个
waitStatus <= 0
的节点,将找到的 Pred 节点和当前 Node 关联,将当前 Node 设置为 CANCELLED。注意,waitStatus 为负值表示节点处于有效等待状态
-
根据当前节点的位置,考虑以下三种情况:
-
当前节点是尾节点。
-
当前节点是Head的后继节点。
-
当前节点不是Head的后继节点,也不是尾节点。
-
根据上述第二条,我们来分析每一种情况的流程:
-
场景一:当前节点是尾节点
-
场景二:当前节点是Head的后继节点
-
场景三:当前节点不是Head的后继节点,也不是尾节点
通过上面的流程,我们对于 CANCELLED 节点状态的产生和变化已经有了大致的了解,但是为什么所有的变化都是对 next 指针进行了操作,而没有对 prev 指针进行操作呢?什么情况下会对 prev 指针进行操作呢?
在执行 cancelAcquire()
的时候,当前节点的前置节点可能已经从队列中出去了(已经执行过 try 代码块中的 shouldParkAfterFailedAcquire 方法了)。如果此时修改 prev 指针,有可能会导致 prev 指向另一个已经移除队列的 Node,因此,在 cancelAcquire()
中修改 prev 指针不安全。
在 shouldParkAfterFailedAcquire()
方法中,会执行下面的代码,其实就是在处理 prev 指针。
node.prev = pred = pred.prev;
shouldParkAfterFailedAcquire()
只有在获取锁失败的情况下才会执行,进入该方法后,说明共享资源已被获取,当前节点之前的节点都不会出现变化,因此这个时候变更 prev 指针比较安全。
释放资源:release
释放资源相比于获取资源来说,会简单许多。在 AQS 中只有一小段实现。
release()
的源码如下:
class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
public final boolean release(long arg) {
if (tryRelease(arg)) { // 释放成功
Node h = head; // 保存头节点
if (h != null && h.waitStatus != 0) // 头节点不为空并且头节点状态不为0
unparkSuccessor(h); // 释放头节点的后继节点
return true;
}
return false;
}
// 释放头节点的后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus; // 获取node节点的等待状态
if (ws < 0) // 状态值小于0,为SIGNAL = -1 或 CONDITION = -2 或 PROPAGATE -3
compareAndSetWaitStatus(node, ws, 0); // 比较并且设置节点等待状态,设置为0
Node s = node.next; // 获取node节点的下一个节点
if (s == null || s.waitStatus > 0) { // 下一个节点为空或者下一个节点的等待状态大于0,即为 CANCELLED
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) // 从尾节点开始从后往前开始遍历
if (t.waitStatus <= 0) // 找到等待状态小于等于 0 的节点,找到最前的状态小于等于 0 的节点
s = t; // 保存节点
}
if (s != null) // 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点 unpark
LockSupport.unpark(s.thread);
}
...
}
AQS 中 tryRelease 的默认实现是抛出异常。
如果 tryRelease()
成功释放了锁,那么,接下来会检查队列的头节点。如果头节点存在并且 waitStatus 不为 0(这意味着有线程在等待),那么会调用 unparkSuccessor()
方法来唤醒等待的线程。
AQS 应用场景
AQS 作为并发编程的框架,为很多其他同步工具提供了良好的解决方案。下面列出了 JUC 中的几种同步工具,大体介绍一下 AQS 的应用场景:
同步工具 | 同步工具与AQS的关联 |
---|---|
ReentrantLock | 使用 AQS 保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock 记录当前获得锁的线程标识, 用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。 |
Semaphore | 使用 AQS 同步状态来保存信号量的当前计数。tryRelease 会增加计数,acquireShared 会减少计数。 |
CountDownLatch | 使用 AQS 同步状态来表示计数。计数为0时,所有的 acquire 操作(CountDownLatch 的 await 方法)才可以通过。 |
ReentrantReadWriteLock | 使用 AQS 同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数。 |
ThreadPoolExecutor | Worker 利用 AQS 同步状态实现对独占线程变量的设置(tryAcquire和tryRelease)。 |
自定义同步工具
这里,我们可以通过利用 AQS 实现一个同步工具。
【示例】
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class LeeLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
利用自定义的同步 Lock,完成一定的同步功能:
public class LeeMain {
static int count = 0;
static LeeLock leeLock = new LeeLock();
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
System.out.println("thread start ...");
try {
leeLock.lock();
for (int i = 0; i < 10000; i++) {
count++;
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
leeLock.unlock();
}
System.out.println("thread end ...");
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}
示例输出:
thread start ...
thread start ...
thread end ...
thread end ...
20000
AQS 总结
常见的问题
Q:某个线程获取锁失败的后续流程是什么呢?
A:存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。
Q:既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
A:是CLH变体的FIFO双端队列。
Q:处于排队等候机制中的线程,什么时候可以有机会获取锁呢?
A:可以参考前面的分析。
Q:如果处于排队等候机制中的线程一直无法获取锁,需要一直等待么?还是有别的策略来解决这一问题?
A:线程所在节点的状态会变成取消状态,取消状态的节点会从队列中释放。
Q:Lock函数通过Acquire方法进行加锁,但是具体是如何加锁的呢?
A:AQS的Acquire会调用tryAcquire方法,tryAcquire由各个自定义同步器实现,通过tryAcquire完成加锁过程。
小结
对于 AbstractQueuedSynchronizer 的分析,最核心的就是 sync queue 的分析。
-
每一个节点都是由前一个节点唤醒;
-
当节点发现前驱节点是 head 并且尝试获取成功,则会轮到该线程运行;
-
condition queue中的节点向sync queue中转移是通过signal操作完成的;
-
当节点的状态为SIGNAL时,表示后面的节点需要运行。
参考: