AQS是什么
AQS(AbstractQueuedSynchronizer),抽象队列同步器,基于MESA模型实现,是一个位于java.util.concurrent.locks包下的抽象类。
大多数同步器实现都是围绕共同的基础行为,如等待队列,条件队列,独占获取,共享获取等,而这些行为的抽象是基于AQS实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。 JDK中提供的大多数同步器如Lock,Latch,Barrier等,都是基于AQS框架来实现的。
- 一般是通过一个内部类Sync继承AQS
- 将同步器所有调用都映射到Sync对应的方法
MESA模型
MESA模型是管程的一种实现策略。
MESA模型的组成
MESA模型由4部分组成:
- enterQueue:
管程的入口队列,当线程在申请进入管程中发现管程已被占用,那么就会进入该队列并阻塞。 - varQueue:
条件变量等待队列,在线程执行过程中(已进入管程),条件变量不符合要求,线程被阻塞时会进入该队列。 - condition variables:
条件变量,存在于管程中,一般由程序赋予意义,程序通过判断条件变量执行阻塞或唤醒操作。 - 阻塞和唤醒机制:wait()和await()就是阻塞操作。notify()和notifyAll()就是唤醒操作。
步骤
(1) 多个线程进入入口等待队列enterQueue,JVM会保证只有一个线程能进入管程内部,Synchronized中进入管程的线程随机。
(2)进入管程后通过条件变量判断当前线程是否能执行操作,如果不能,则调用阻塞方法。反之,执行相应操作。
(3)条件变量调用阻塞方法,将当前线程放入varQueue,等待其他线程唤醒,返回enterQueue。
(4)执行相应操作,执行完毕后调用notify/notifyAll等唤醒操作,唤醒对应varQueue中的一个或多个等待线程。
(5)被唤醒的线程会从varQueue放入enterQueue中。
(6)被唤醒的线程不会立即执行,会被放入enterQueue,等待JVM下一次选择运行,而正在运行的线程会继续执行,直到程序执行完毕。
Java针对管程有两种实现
- 基于Object的Monitor机制,用于synchronized内置锁的实现
- 抽象队列同步器AQS,用于JUC包下的Lock锁机制的实现
AQS的实现
案例
以ReentrantLock为例
```java
/**
* Synchronization implementation for ReentrantReadWriteLock.
* Subclassed into fair and nonfair versions.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
.....
核心结构
同步状态
使用共享变量state,volatile修饰保证线程可见性,state用于子类实现加锁与解锁的逻辑。 这个状态的具体含义取决于AQS的子类如何使用它。例如,在ReentrantLock中,它表示持锁的计数;在Semaphore中,它表示剩余的许可数。
/**
* The synchronization state.
*/
private volatile int 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;
}
节点
AQS内部使用队列来维持线程的排队状态,它是一个虚拟的双向队列(双向链表)。每个参与同步状态竞争的线程都被封装成一个节点(Node类)并加入此队列。
节点状态:
-
值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
-
CANCELLED,值为1,表示当前的线程被取消;
-
SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
-
CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
-
PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
AQS 依赖CLH同步队列来完成同步状态的管理:
-
当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其 加入到CLH同步队列,同时会阻塞当前线程
-
当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
-
通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列)
条件等待队列
AQS中条件队列是使用单向链表保存的,用nextWaiter来连接:
-
调用await方法阻塞线程;
-
当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)
获取和释放同步状态
AQS定义了模板方法acquire和release,这些方法分别用于获取和释放同步状态。具体行为则通过覆盖tryAcquire和tryRelease等方法实现。
- 获取状态:acquire方法调用tryAcquire尝试直接获取资源,失败则将线程加入等待队列。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
- 释放状态:release方法调用tryRelease来改变同步状态,并尝试唤醒队列中的后续节点。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
以ReentrantLock为例,说明入队与出队的情况。
总结
AQS的核心思想是基于一个volatile类型的int成员变量(称为state)来构造锁的具体实现,以及等待队列来完成对锁的分配。当线程尝试获取同步状态时,AQS会利用模板方法模式,让子类去定义获得许可的状态变更是否应该发生。如果由于某些原因(例如状态不可用)导致获取失败,则会将请求放入到一个CLH(Craig-Landin-Hagersten)类型的FIFO(先进先出)等待队列中,并阻塞对应的线程。
AQS支持两种基本的获取共享资源模式:
- 独占模式:只有一个线程可以执行,如ReentrantLock。
- 共享模式:多个线程可以同时执行,如Semaphore或CountDownLatch。
AQS的主要作用包括但不限于:
- 提供了内部队列来管理等待的线程。
- 定义了获取和释放同步状态的基本方法。
- 允许子类通过继承来实现特定的同步语义。
很多常见的Java并发工具类,如ReentrantLock, ReentrantReadWriteLock, Semaphore, CountDownLatch等,都是基于AQS来实现的。