1、AQS是什么

在java.util.concurrent.locks包下,有三个抽象类,分别是:
AbstractOwnableSynchronizer、AbtractQueuedLongSynchronizer、AbstractQueuedSynchronizer。
通常我们把AbstractQueueSunchronizer,抽象队列同步器简称为AQS。
它实现了一个先进先出的队列,底层实现的数据机构是一个双向链表。
AQS是一个用来构建锁和同步器的框架,其内部实现的关键是:先进先出的队列和state状态。
Lock接口的实现类中有两个相关的锁,ReentrantLock和ReadWriteLock,是基于AQS构建的。
简单介绍一下AQS就是:
是一个构建锁或者其他同步器组件的重量级基础框架,时整个JUC体系的基石。它通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型的变量表示持有锁的状态。

我们使用JUC下的一些类,如ReentrantLock、CountDownLatch、ReentrantReadWriteLock、CyclicBarrier、Semphore都是基于AQS框架的。
image.png

AQS的核心思想:
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。这个机制在AQS中,是用CLH队列锁实现的,即:将暂时获取不到锁的线程加入到队列中。
AQS使用一个int类型的成员变量来表示同步状态(这个成员变量被volatile修饰,保证线程的可见性),通过内置的队列来完成获取资源线程的排队工作。AQS将请求共享资源的线程封装成队列的节点(AQS的内部类Node)来实现锁的分配,通过CAS、自旋以及LickSupport.park()的方式,维护state变量的状态(也就是对该同步状态进行原子操作),实现对其值的修改(也就是使并发达到同步到控制效果)。

CLH队列:
AQS内部维护着一个先进先出的队列,这个队列就是CLH队列。CLH队列全程是:Craig、Landin、andHagersten lock queue,CLH队列是先进先出的双端双向队列,实现公平锁(同时也是自旋锁)。线程通过AQS获取锁失败,就会将线程封装成一个Node节点,插入队列尾,当有线程释放锁时,尝试把队列头的next节点占用锁。
CLH队列图示:
image.png

看一下ReentrantLock和AQS的关系:
image.png

2、进一步理解锁和同步器的关系

锁:面向锁的使用者,定义了程序员和锁交互层的使用层API,调用API即可;
同步器:面向锁的实现者,比如juc包的源码开发者Doug Lee,提出统一规范并简化锁的实现,屏蔽了同步管理状态、阻塞线程排队和通知、唤醒机制等。

3、AQS中的队列与状态

有上面的描述可以看出来,AQS中比较重要的就是它维护着的队列(CLH变种的双端队列)和一个状态(state)。

—> 有阻塞就需要排队,实现排队必然需要队列。

3.1 总览

我们知道,AQS等同于一个状态位+定义的一个双端队列,请求资源的线程会被封装成队列的节点Node,同时,在AQS中又维护者队列的头结点和尾部。
AQS同步队列的基本结构:
image.png

3.2 AQS中的同步状态

同步状态时AQS中的一个成员变量,定义如下:

  1. /**
  2. * The synchronization state.
  3. */
  4. private volatile int state;

state为0时表示没有被占用,为1表示被占用。
可以看到,它使用volatile关键字修饰,保证了线程的可见性。
修改state值时,通过CAS算法实现:

/**
     * 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);
    }

3.3 AQS中的队列

AQS中维护的这个队列被称为CLH队列,是一个双向队列。
既然是队列,自然也是先进先出的,用来让线程等待。
AQS中的CLH队列的节点由ASQ的静态内部类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;
static final int PROPAGATE = -3;

/**
* CANCELLED,值为1,表示当前的线程被取消
* SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
* CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
* PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
* 值为0,表示当前节点在sync队列中,等待着获取锁。
*/
volatile int waitStatus;

// 前驱结点
volatile Node prev;

// 后继结点
volatile Node next;

// 与该结点绑定的线程
volatile Thread thread;

// 存储condition队列中的后继节点
Node nextWaiter;

// 是否为共享模式
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中,有两个常量SHARE和EXCLUSVIE,分别用于表示这个节点支持共享模式还是独占模式。
共享模式是指允许多个线程获取同一个锁而且可能获取成功,独占模式是指一个锁如果被一个线程持有,其他线程必须等待。
多个线程读取一个文件可以采用共享模式,而当有一个线程在写文件时,不会允许另一个线程写这个文件,这就是独占模式的应用场景。
可以看到,Node基本上可以看作等同于waitStatus+前后指针的指向+绑定的线程。
Node节点的等待状态:waitStatus成员变量

4、AQS的一些总结