概念
AQS,全称AbstractQueuedSynchronizer,即抽象队列同步器,和CAS共同撑起了整个java.util.concurrent包,同时也是Java并发编程上绕不开的一个概念
抽象队列同步器,以下统称AQS,用于解决的就是多线程并发访问控制问题。在传统的多线程编程中,如果有多个线程需要访问同一个变量,就需要使用synchronized来为临界区加锁(临界区:访问共享资源的程序段),但是这种方式既不“优雅”,也不高效(即使Java为其已经做了很多优化),更重要的是,不能实现更细粒度的控制(虽然可以通过大量额外程序代码实现)。这时候,AQS出现了,它提供了一种简洁优雅的机制来实现线程安全
本质上说,AQS是构建(包括锁在内)大部分同步组件的基础框架,它通过管理资源状态量和线程同步队列来实现资源的分发(如共享或独占)。接下来,我们就要对其实现方式来做进一步的讨论
内部组件
AQS的实现是基于同步状态量和一个FIFO的双向队列来实现的,下面就来分别讲述其各自的特点
同步状态
在类内部有一个被volatile修饰的整形变量state,其定义如下:
private volatile int state;
这个变量官方称为同步状态量,实际可以理解为一些共享资源,每有一个线程获取到了一个共享资源,则这个同步状态量就要减一,反之就需要加一,如果这个状态量为0,就表示共享资源已经被其他所有线程分完了,当前的线程只能等待
但是同步状态量并不能直接与资源划等号,它只是提供一种类似门禁的操作(可以类比锁),任何线程想要获取共享资源都需要先来询问这个同步状态量是否允许这样的操作,这也就间接实现了对于共享资源的线程安全控制
在AQS中,对于该变量提供了以下三个操作接口:
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
/**
* 通过CAS操作来更新state变量
*/
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
这里暂且提一句,CAS指的是CompareAndSwap,即比较并替换,是一种乐观锁的实现,其实现原理简单的说就是在更新一个值之前,先比较变量内存地址的当前值,是否与这个地址上预期应存储的值一致,如果一致再进行更新。为了不喧宾夺主,关于CAS的东西暂且就说这么多,对于本文来说了解这些就已经够用了
然后再回来看这三个接口方法,其均被protected修饰符所修饰,所以这些接口方法并不是提供给用户调用的,而是供同步框架的开发者使用。这三个方法为同步状态量的修改操作提供了极大的控制权限,因此也需要谨慎使用
双向队列
AQS为了管理所有获取或没获取到同步状态的线程,使用了双向队列来管理这些线程,这个队列的节点定义如下:
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;
/** 线程正在等待Condition */
static final int CONDITION = -2;
/** 传播状态,表示下一次获取共享同步状态的操作会无条件传播下去 */
static final int PROPAGATE = -3;
/** 节点的等待状态 */
volatile int waitStatus;
/** 前驱节点 */
volatile Node prev;
/** 后继节点 */
volatile Node next;
/** 队列节点所代表的线程 */
volatile Thread thread;
/** 等待队列中的后继节点,如果在共享模式下等待,则该变量为SHARED */
Node nextWaiter;
/** 返回节点是否以共享模式在等待资源 */
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回当前节点的前驱节点,如果不存在则抛出异常
* @return 前驱节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p