AQS
讲讲AQS
简单说AQS就是起到了一个抽象,封装的作用,将一些排队,入队,加锁,中断等方法提供出来,便于其它相关JUC锁的使用,具体加锁时机,入队时机等都需要实现类自己控制。
英文全称 是AbstractQueuedSynchronizer,AQS的核心是一个FIFO的双向队列,队列中的每个节点都代表一个线程。AQS提供了获取锁和释放锁的基本框架,具体的锁实现(如ReentrantLock、CountDownLatch等)需要继承AQS并实现其抽象方法。
它主要通过维护一个共享状态(state)和一个先进先出(FIFO) 的等待队列,来管理线程对共享资源的访问。
其中 state用volatile修饰,表示当前资源的状态,通常是一个整数值。AQS通过CAS操作来更新这个状态,以确保线程安全。
当线程尝试获取资源失败的时候,会被加入到AQS的等待队列中,这个队列是一个变体的CLH队列,采用双向链表结构,节点包含线程的引用,等待状态以及前驱和后继节点的指针。
AQS的常见实现类包括 ReentrantLock
、CountDownLatch
、Semaphore
等,这些类都继承自AQS,并实现了其抽象方法。
AQS的核心机制
- 状态 AQS通过一个volatile类型的证书state表示同步状态。
子类可以通过 getState()
、setState(int newState)
、compareAndSetState(int expect, int update)
等方法来获取和修改这个状态。\
状态可以表示多种含义,例如在ReentrantLock
中,state表示锁的重入次数;在CountDownLatch
中,state表示计数器的值。
- 等待队列
AQS维护了一个FIFO的等待队列,用于管理等待获取同步状态的线程,
每个节点都是一个
Node
对象,代表一个等待的线程,节点之间通过next和prev指针连接。
|
|
当一个线程获取同步状态失败的时候,会被添加到等待队列中,自选等待或者被阻塞,直到钱买你的线程释放同步状态。
- 独占模式和共享模式 AQS支持两种获取同步状态的模式:独占模式和共享模式。
- 独占模式:只有一个线程可以获取同步状态,其他线程需要等待。
ReentrantLock
就是使用独占模式。 - 共享模式:多个线程可以同时获取同步状态,直到达到某个限制。
CountDownLatch
和Semaphore
使用共享模式。
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来实现锁的功能。