同步工具的底层依赖:AQS
文章目录
目录
文章目录
前言
一、什么是AQS?
二、AQS原理
1.AQS框架
2.AQS原理
3.AQS数据结构
4.同步状态State
5.AQS 的核心流程(以独占模式为例)
1. 资源获取(acquire(int arg))
2. 资源释放(release(int arg))
三、AQS 在 JUC 中的典型应用
四、AQS 的核心优势
总结
前言
在 JDK 1.5 之前,Java 的同步机制主要依赖synchronized
关键字和Object
类的wait()/notify()
方法。这些机制虽然简单易用,但存在明显局限性;随着 Java 并发场景的复杂化(如多线程协作、高并发控制),需要更灵活、高效的同步工具。JDK 团队(以并发领域专家Doug Lea为核心)意识到:不同同步工具(锁、信号量、倒计时器等)的底层逻辑存在共性,可以抽象为一套通用框架。JDK 1.5 正式引入java.util.concurrent
(JUC)包,标志着 Java 并发编程进入新阶段。AQS 作为 JUC 的 “基础设施”,被内置其中,成为几乎所有同步工具的底层依赖可以说,AQS 的出现让 JUC 包的同步工具实现变得 “模块化”。底层通用逻辑由 AQS 负责,上层具体规则由各工具自己定义(通过重写 AQS 的钩子方法)。这种设计极大降低了同步工具的开发难度,同时保证了性能和可靠性。
一、什么是AQS?
在Java的核心类库中,Lock、Semaphore、ReentrantLock等线程同步类,都是基于AbstractQueuedSynchronizer(简称为AQS)实现。
AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程等功能,并提供等待队列模型的线程同步框架。
AQS 解决了并发编程中最核心的问题:如何协调多个线程对共享资源的竞争。它通过封装通用的同步逻辑(如队列管理、线程阻塞),让开发者可以更简单地实现各种同步工具(无需重复处理复杂的线程交互细节) 例如,JUC 中的ReentrantLock
(可重入锁)、Semaphore
(信号量)、CountDownLatch
(倒计时器)、CyclicBarrier
(循环屏障)等,其底层都是基于 AQS 实现的。
二、AQS原理
1.AQS框架
总体来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。
2.AQS原理
AQS核心思想是:如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要实现等待机制,来保证锁分配。 这种等待机制主要是基于CLH队列(一种虚拟双向队列)实现,将暂时获取不到锁的线程加入到队列中,实现队列等待。
CLH是Craig、 Landin 、 Hagersten三个人发明的一种基于链表的队列,AQS中的队列是CLH变体,一种虚拟双向队列(FIFO),虚拟双向队列的意思是不存在队列实例,仅存在结点之间的关联关系。
AQS通过将每条请求共享资源的线程,封装成CLH队列的一个节点来实现锁的分配。同时,AQS使用一个volatile修饰的int类型全局成员变量state来表示同步状态,在排队加锁的过程中,通过CAS完成对state值的修改。
3.AQS数据结构
AQS中最基本的数据结构Node:CLH变体队列中的节点
4.同步状态State
AQS中维护了一个名为state的字段,意思是同步状态,使用volatile关键字修饰,用于展示当前共享资源的获锁情况。
//共享变量,使用volatile修饰保证线程可见性
private volatile int state;
方法名 描述
protected final int getState() 获取State的值
protected final void setState(int newState) 设置State的值
protected final boolean compareAndSetState(int expect, int update) 使用CAS方式更新State
我们可以通过修改State字段表示的同步状态,实现多线程的独占模式和共享模式(加锁过程)。
独占模式:
共享模式:
5.AQS 的核心流程(以独占模式为例)
AQS 的工作流程围绕 “资源获取” 和 “资源释放” 展开,以下是独占模式下的完整流程:
1. 资源获取(acquire(int arg)
)
线程通过acquire(arg)
尝试获取资源,流程如下:
public final void acquire(int arg) {// 1. 调用子类重写的tryAcquire()尝试获取资源if (!tryAcquire(arg) && // 2. 若失败,将线程封装为Node加入队列(enq()),并阻塞线程(acquireQueued())acquireQueued(enq(new Node(Thread.currentThread(), Node.EXCLUSIVE)), arg)) {// 3. 若线程在阻塞中被中断,记录中断状态selfInterrupt();}
}
-
- 尝试获取资源:调用
tryAcquire(arg)
(子类实现),若返回true
(获取成功),直接返回; - 入队操作:若获取失败,通过
enq(Node)
将线程封装为 Node 添加到队列尾部(FIFO);enq()
通过 CAS 循环确保节点被正确添加(处理并发入队);
- 阻塞等待:
acquireQueued(Node, arg)
让节点中的线程进入阻塞状态,等待被唤醒;- 线程阻塞前会再次尝试获取资源(避免不必要的阻塞);
- 若前驱节点是头节点且获取资源成功,则当前节点成为新头节点;
- 中断处理:若线程在阻塞中被中断,不会立即响应中断,而是记录中断状态(由上层决定是否处理)。
- 尝试获取资源:调用
2. 资源释放(release(int arg)
)
持有资源的线程通过release(arg)
释放资源,流程如下:
public final boolean release(int arg) {// 1. 调用子类重写的tryRelease()尝试释放资源if (tryRelease(arg)) {// 2. 释放成功,唤醒队列中的后继线程Node h = head;if (h != null && h.waitStatus != 0) {unparkSuccessor(h); // 唤醒后继节点}return true;}return false;
}
- 尝试释放资源:调用
tryRelease(arg)
(子类实现),若返回true
(释放成功),继续; - 唤醒后继线程:通过
unparkSuccessor(Node)
唤醒头节点的后继节点(SIGNAL
状态的节点);- 唤醒前会将后继节点的状态重置为
0
; - 被唤醒的线程会重新尝试获取资源(进入
acquireQueued()
的循环)。
- 唤醒前会将后继节点的状态重置为
三、AQS 在 JUC 中的典型应用
JUC 中的核心同步工具均基于 AQS 实现,以下是常见工具的底层逻辑:
工具类 | 模式 | state 含义 | 核心逻辑 |
---|---|---|---|
ReentrantLock | 独占 | 重入次数(0 = 未锁定) | tryAcquire 实现公平 / 非公平锁竞争,tryRelease 递减重入次数。 |
Semaphore | 共享 | 剩余许可数 | tryAcquireShared 减少许可,tryReleaseShared 增加许可。 |
CountDownLatch | 共享 | 倒计时剩余次数 | tryAcquireShared 判断次数是否为 0,tryReleaseShared 递减次数(到 0 唤醒所有线程)。 |
ReadWriteLock | 读写分离 | 高 16 位 = 读计数,低 16 位 = 写计数 | 读锁共享(tryAcquireShared ),写锁独占(tryAcquire )。 |
四、AQS 的核心优势
- 通用性:通过模板方法封装共性逻辑,支持多种同步场景(独占 / 共享);
- 高效性:基于 CAS 和双向链表,避免锁竞争,性能优于
synchronized
(早期版本); - 灵活性:支持超时获取、可中断获取、公平 / 非公平模式等高级特性;
- 可靠性:严格的 FIFO 队列保证线程竞争的公平性,避免饥饿问题。
总结
AQS 是 Java 并发编程的 “基石”,其核心思想是 **“状态管理 + 队列排队 + 模板方法”**。通过理解 AQS 的结构(state
、CLH 队列)、工作模式(独占 / 共享)和核心流程(获取 / 释放资源),能深入掌握 JUC 同步工具的底层原理,甚至自定义符合业务需求的同步工具。AQS 的设计体现了 “抽象共性、隔离变化” 的软件设计思想,是并发编程领域 “高内聚、低耦合” 的典范。
好了,今天就到这里,今天依旧是深蹲不写BUG,我们一起加油努力!!!