AQS 基础
什么是 AQS
想必大家都对其不怎么陌生,面试常考的一个知识点。
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
特点:
-
用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState - 获取 state 状态
- setState - 设置 state 状态
- compareAndSetState - cas 机制设置 state 状态
- 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
-
提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
-
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet
子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
获取锁
// 如果获取锁失败
if (!tryAcquire(arg)) {
// 入队, 可以选择阻塞当前线程 park unpark
}
释放锁
// 如果释放锁成功
if (tryRelease(arg)) {
// 让阻塞线程恢复运行
}
AQS中有哪些数据结构
首先,我想说的是AQS 其实就是想仿照Synchnoized 做一个同步锁,并且将Synchnoized 的缺点进行优化,进行改进,所以两者联系起来去看这些东西就没有这么复杂了。
- 首先是有一个 条件变量的内部类
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
内部实现了 Condition 的接口,里面的节点就是存放线程的,需要等待的线程。和Synchnoized 一样这个条件变量就相当于 其 中的 维护的一个WaitSet ,当需要的时候进行唤醒,但是不同的是,一个锁可以new 很多个条件变量,就是相当于在不同的休息室,可以精准唤醒, 而Synchnoized 不行,只有一个休息室。
- 状态码,这个是指锁的状态码。
private volatile int state;
锁的状态码 初始都是1 ,当有人获取就使用CAS 修改其状态码。通过状态码来判断是否阻塞中或者是否可重入。
- 内部还维护了一个阻塞队列,就是获取不到锁的线程进行阻塞
private transient volatile Node head;
private transient volatile Node tail;
- Node 内部类,就是等待队列和阻塞队列中所维护的节点,
static final class Node
这四个应该是比较重要的部分,接下来会逐个去分析
Node 节点
这个内部类 就是线程在队列中的状态,需要维护的节点,所以这个状态码是Node 节点中比较核心的一个关键点,代表这需不要被唤醒。
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled. */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking. */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition. */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate.
*/
static final int PROPAGATE = -3;
- CANCELLED:表示线程取消了等待。如果取得锁的过程中发生了一些异常,则可能出现取消的情况,比如等待过程中出现了中断异常或者出现了timeout。
- SIGNAL:表示后续节点需要被唤醒。
- CONDITION :线程等待在条件变量队列中。
- PROPAGATE :在共享模式下,无条件传播releaseShared状态。早期的JDK并没有这个状态,咋看之下,这个状态是多余的。引入这个状态是为了解决共享锁并发释放引起线程挂起的bug 6801020。(随着JDK的不断完善,它的代码也越来越难懂了 😦,就和我们自己的工程代码一样,bug修多了,细节就显得越来越晦涩)
其中CANCELLED=1,SIGNAL=-1,CONDITION=-2,PROPAGATE=-3 。在具体的实现中,就可以简单的通过waitStatus释放小于等于0,来判断是否是CANCELLED状态。
Condition 条件变量内部类的实现
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
内部维护了一个等待的队列。
我们看看一些主要的方法吧
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
await 方法, 这个方法调用各种函数,不好展开说,太多了,但是表达的意思就是维护了一个等待队列,底层停止等待使用的是 LockSupport.park() ,帮助线程暂停。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
唤醒队列中的一个线程
总结
- AQS ,同步器,实现了JUC 中的核心组件
- 解决了子类实现同步器时涉及到的大量细节问题,例如获取同步方法,FIFO同步队列
- 采用模板方法模式,AQS 实现了大量的通用方法,子类通过继承方式实现其抽象方法来管理同步状态
- CLH 同步队列 :FIFO 双向队列,AQS 依赖它来解决同步状态的管理问题 , 首节点唤醒,等待队列加入到CLH同步队列的尾部
- 同步状态获取和释放:
- 独占式 :
- 获取锁 :
1. 获取同步状态:acquire
2. 相应中断: qcquireInterruptibly
3. 超时获取: tryAcquireNanos - 释放锁: release
- 获取锁 :
- 共享式 :
- 获取锁: acquireShared
- 释放锁: releaseShared
- 独占式 :
- 线程的阻塞和唤醒
- 当有线程获取锁了,其他再次获取是需要阻塞,当线程释放锁后,AQS 负责唤醒线程
- LockSupport
- 是用来创建和其他同步类的基本线程阻塞原语
- 是个使用LockSupport 的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用 park 将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用unpark 使其可用
- park () , unpark()
AQS 基本是提供模板方法,通过后代的子类扩展然后实现同步的一个类,但是一定要了解一些基本了原理,和一些基本的方法进行实现,一些状态码会在后代的实现,主要使用的是状态码 利用CAS 机制进行修改状态。