讲讲AQS

简单说AQS就是起到了一个抽象,封装的作用,将一些排队,入队,加锁,中断等方法提供出来,便于其它相关JUC锁的使用,具体加锁时机,入队时机等都需要实现类自己控制。

英文全称 是AbstractQueuedSynchronizer,AQS的核心是一个FIFO的双向队列,队列中的每个节点都代表一个线程。AQS提供了获取锁和释放锁的基本框架,具体的锁实现(如ReentrantLock、CountDownLatch等)需要继承AQS并实现其抽象方法。

它主要通过维护一个共享状态(state)和一个先进先出(FIFO) 的等待队列,来管理线程对共享资源的访问。

其中 state用volatile修饰,表示当前资源的状态,通常是一个整数值。AQS通过CAS操作来更新这个状态,以确保线程安全。

当线程尝试获取资源失败的时候,会被加入到AQS的等待队列中,这个队列是一个变体的CLH队列,采用双向链表结构,节点包含线程的引用,等待状态以及前驱和后继节点的指针。

AQS的常见实现类包括 ReentrantLockCountDownLatchSemaphore等,这些类都继承自AQS,并实现了其抽象方法。

AQS的核心机制

  1. 状态
    AQS通过一个volatile类型的证书state表示同步状态。

子类可以通过 getState()setState(int newState)compareAndSetState(int expect, int update)等方法来获取和修改这个状态。\

状态可以表示多种含义,例如在ReentrantLock中,state表示锁的重入次数;在CountDownLatch中,state表示计数器的值。

  1. 等待队列
    AQS维护了一个FIFO的等待队列,用于管理等待获取同步状态的线程,
    每个节点都是一个Node对象,代表一个等待的线程,节点之间通过next和prev指针连接。

    /** CLH Nodes */
    abstract static class Node {
        volatile Node prev;       // initially attached via casTail
        volatile Node next;       // visibly nonnull when signallable
        Thread waiter;            // visibly nonnull when enqueued
        volatile int status;      // written by owner, atomic bit ops by others

        // methods for atomic operations
        final boolean casPrev(Node c, Node v) {  // for cleanQueue
            return U.weakCompareAndSetReference(this, PREV, c, v);
        }
        final boolean casNext(Node c, Node v) {  // for cleanQueue
            return U.weakCompareAndSetReference(this, NEXT, c, v);
        }
        final int getAndUnsetStatus(int v) {     // for signalling
            return U.getAndBitwiseAndInt(this, STATUS, ~v);
        }
        final void setPrevRelaxed(Node p) {      // for off-queue assignment
            U.putReference(this, PREV, p);
        }
        final void setStatusRelaxed(int s) {     // for off-queue assignment
            U.putInt(this, STATUS, s);
        }
        final void clearStatus() {               // for reducing unneeded signals
            U.putIntOpaque(this, STATUS, 0);
        }

        private static final long STATUS
            = U.objectFieldOffset(Node.class, "status");
        private static final long NEXT
            = U.objectFieldOffset(Node.class, "next");
        private static final long PREV
            = U.objectFieldOffset(Node.class, "prev");
    }

当一个线程获取同步状态失败的时候,会被添加到等待队列中,自选等待或者被阻塞,直到钱买你的线程释放同步状态。

  1. 独占模式和共享模式
    AQS支持两种获取同步状态的模式:独占模式和共享模式。
  • 独占模式:只有一个线程可以获取同步状态,其他线程需要等待。ReentrantLock就是使用独占模式。
  • 共享模式:多个线程可以同时获取同步状态,直到达到某个限制。CountDownLatchSemaphore使用共享模式。

AQS框架

上图中有颜色的为Method,无颜色的为Attribution。

总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。

当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。

下面我们会从整体到细节,从流程到方法逐一剖析AQS框架,主要分析过程如下:

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。

CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

主要原理图如下:

AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。

先来看下AQS中最基本的数据结构——Node,Node即为上面CLH变体队列中的节点。

ReentrantLock

https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html

我们可以通过ReentrantLock来看看AQS的具体实现。

ReentrantLock和AQS的关联

ReentrantLock是AQS的一个具体实现类,它通过继承AQS来实现锁的功能。