简说java-AQS

176 阅读6分钟

AQS是什么

AQS(AbstractQueuedSynchronizer),抽象队列同步器,基于MESA模型实现,是一个位于java.util.concurrent.locks包下的抽象类。

大多数同步器实现都是围绕共同的基础行为,如等待队列,条件队列,独占获取,共享获取等,而这些行为的抽象是基于AQS实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。 JDK中提供的大多数同步器如Lock,Latch,Barrier等,都是基于AQS框架来实现的。

  • 一般是通过一个内部类Sync继承AQS
  • 将同步器所有调用都映射到Sync对应的方法

MESA模型

MESA模型是管程的一种实现策略。

image.png 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类)并加入此队列。

image.png 节点状态:

  1. 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。

  2. CANCELLED,值为1,表示当前的线程被取消;

  3. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;

  4. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;

  5. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;

同步等待队列

image.png

AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。

AQS 依赖CLH同步队列来完成同步状态的管理:

  • 当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其 加入到CLH同步队列,同时会阻塞当前线程

  • 当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

  • 通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列)

条件等待队列

image.png

AQS中条件队列是使用单向链表保存的,用nextWaiter来连接:

  • 调用await方法阻塞线程;

  • 当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)

image.png

获取和释放同步状态

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为例,说明入队与出队的情况。

image.png

总结

AQS的核心思想是基于一个volatile类型的int成员变量(称为state)来构造锁的具体实现,以及等待队列来完成对锁的分配。当线程尝试获取同步状态时,AQS会利用模板方法模式,让子类去定义获得许可的状态变更是否应该发生。如果由于某些原因(例如状态不可用)导致获取失败,则会将请求放入到一个CLH(Craig-Landin-Hagersten)类型的FIFO(先进先出)等待队列中,并阻塞对应的线程。

AQS支持两种基本的获取共享资源模式:

  1. 独占模式:只有一个线程可以执行,如ReentrantLock。
  2. 共享模式:多个线程可以同时执行,如Semaphore或CountDownLatch。

AQS的主要作用包括但不限于:

  • 提供了内部队列来管理等待的线程。
  • 定义了获取和释放同步状态的基本方法。
  • 允许子类通过继承来实现特定的同步语义。

很多常见的Java并发工具类,如ReentrantLock, ReentrantReadWriteLock, Semaphore, CountDownLatch等,都是基于AQS来实现的。