一、AQS是什么
AQS的完整类名是——AbstractQueuedSynchronizer(直译过来就是抽象队列同步器)。从字面上就能看出来,是专门用于实现同步功能的一个抽象框架。事实上,AQS是著名的JDK并发包设计者Doug Lea定义的一套用于控制多线程并发访问共享资源的基础框架,许多并发包工具实现都依赖于它,如常用的ReentrantLock、ReadWriteLock、Semaphore、CountDownLatch和ThreadPoolExecutor 的 Worker。
二、为什么要使用AQS
设想一下,如果没有AQS,要实现一个锁需要如何进行?
如果没有 AQS,那么就需要开发者自己去实现以下内容,至少包括:
- 同步状态的原子性管理(比如锁状态的获取和释放)
- 线程的阻塞与唤醒
- 阻塞队列的管理
举个例子:实现一个类似ReentrantLock功能的可重入锁 ,需要一个变量维护锁被重入的次数,同时必须保证多线程同时操作该变量时是线程安全的;同时竞争锁失败的线程,可能需要让它们陷入阻塞并进行排队,同时要在合适的时机将其唤醒。
其实不只是对于锁而言,其他的进行多线程并发控制的同步组件基本都需要上述这些操作。这一系列的操作流程都是繁杂而重复的,此时AQS的意义就凸显出来了。
AQS定义好了这一套骨架流程的操作,不同的同步组件只需要继承AQS并重写指定的方法,就可以实现同步组件自身的
功能逻辑。这其实就是模板方法的思想。
总结一下,就是:同步组件是面向普通开发者的,AQS是面向同步组件开发者的。
三、AQS 的设计原理
AQS的实现源码有2000多行代码,相对还是比较复杂的。但是我们将其实现细节抛开,其实重点就围绕着以下三大部分:
- state 状态
- CLH队列
- 获取/释放
3.1 state 状态
如果我们的 AQS 想要去管理或者想作为协作工具类的一个基础框架,那么它必然要管理一些状态,而这个状态在 AQS 内部就是用 state 变量去表示的。它的定义如下:
/**
* The synchronization state.
*/
private volatile int state;
而 state可以根据具体组件类的作用不同而表示不同的含义,比如:
- 信号量Semaphore:state 表示剩余许可的数量。比如我们把 state 初始化设置为 5,表示许可证初始一共有5个,然后当某个线程取走1个许可证之后,这个 state 就会变为4,所以信号量的 state 相当于是一个内部计数器。
- CountDownLatch :state 表示是需要“倒数”的数量。一开始我们假设把它设置为 5,当每次调用 CountDown 方法时,state 就会减 1,一直减到 0 的时候就代表这个门闩被放开。
- ReentrantLock :表示的是锁的占有情况。初始化时是 0,表示没有线程占有锁;如果 state 变成了 1,则代表这个锁已经被某个线程所持有了。
- ReadWriteLock:高16位表示读锁状态,低16位表示写锁状态。
下面看AQS提供的state状态的获取和变更操作:
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
1.getState读取——由于state变量带有volatile关键字进行修饰,能保证内存可见性,getState方法获取就能直接最新的值。
2.setState直接写入——直接对state = newState;赋值操作,此操作一般是用于同步组件在独占之后的状态变更设置,无进行额外同步。
3.compareAndSetState(int expect, int update) CAS操作写入,借助Unsafe工具类的 CAS 操作,利用 CPU 指令的原子性保证了这个操作的原子性。
3.2 CLH队列 (FIFO)
CLH队列作用是存储竞争失败从而进入阻塞等待状态的线程。
队列内部是双向链表的结构,分别用 head 和 tail 来表示头尾节点, 初始化的时候两个节点都指向了一个空节点。
头节点当做当前持有锁的线程,尾节点则是每个新的阻塞线程进来队列时要插入的位置,插入尾节点需要通过CAS进行插入操作原子性控制。
(图文引用自AQS英文文档)
3.3 同步状态的获取和释放
AQS定义了独占式获取与释放同步状态和共享式获取与释放同步状态4中操作类型,需要子类自己实现。
四、小结
本文介绍了什么AQS框架出现的背景作用以及AQS核心的设计原理。 AQS是面向同步组件开发者的基础框架,使用模板方法设计模式的思想,将同步状态的管理 、 阻塞队列和线程的阻塞唤醒等处理流程定义成固定的模板。组件开发者无需关注底层细节,只需要重写少数几个相关方法即可。
参考资料:AQS官方文档