北京时间:2026年4月11日|目标读者:技术入门/进阶学习者、在校学生、面试备考者、相关技术栈开发工程师
开篇:AQS——Java并发编程的基石

在Java并发编程的世界里,AbstractQueuedSynchronizer(AQS) 是当之无愧的核心基石。从ReentrantLock到CountDownLatch,从Semaphore到CyclicBarrier,几乎所有常用的同步工具类都构建在AQS之上-1。但很多开发者面临一个共同的痛点:会用这些同步工具,却不懂它们背后的运行原理;面试时能背出“AQS是抽象队列同步器”,但追问到底层实现就开始含糊。 本文将由小星AI助手带你从零开始,系统拆解AQS的核心概念、底层原理和面试考点,配合可运行的代码示例,帮你建立完整的知识链路。
本文讲解范围: 核心组件(state + CLH队列 + 模板方法)→ 两种同步模式(独占/共享)→ 代码实战(ReentrantLock示例)→ 底层原理简析 → 高频面试题。全文围绕一条主线展开:AQS如何用少量核心组件统一管理复杂的线程排队与唤醒。

一、痛点切入:为什么需要AQS?
传统同步方式的局限
在AQS出现之前,开发者要自定义一个锁或同步器,往往需要重复实现一套复杂的线程排队、阻塞和唤醒机制。来看一个用原生synchronized实现计数器同步的示例:
// 传统方式:使用synchronized实现计数器同步 public class Counter { private int count = 0; public synchronized void increment() { count++; // 由JVM管硐排队和阻塞 } public synchronized int getCount() { return count; } }
传统方式的三大痛点
耦合高:同步逻辑和业务逻辑紧密耦合,难以复用。
扩展性差:
synchronized的语义固定(互斥锁),无法定制灵活的同步策略(如共享锁、超时锁)。控制粒度粗:无法实现公平锁/非公平锁的灵活切换,也无法支持Condition式的精确唤醒。
AQS的设计初衷
正是为了解决这些问题,Doug Lea在J.U.C(java.util.concurrent)包中设计了AQS。AQS不直接提供锁,而是提供一个通用的同步框架骨架,让子类通过实现少量方法来定制具体的同步语义,排队、阻塞、唤醒等底层细节全部由AQS统一封装。-3
二、核心概念讲解:AQS是什么?
标准定义
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发包java.util.concurrent.locks中提供的一个抽象类,用于构建依赖先进先出(FIFO)等待队列的阻塞锁及相关同步器-。
拆解关键词
抽象(Abstract) :AQS是抽象类,不提供具体锁的实现,而是定义好骨架,由子类填空。
队列(Queue) :内部维护一个FIFO双向队列,管理所有等待获取资源的线程。
同步器(Synchronizer) :它是一套协调多线程访问共享资源的机制。
生活化类比
可以把AQS想象成 “奶茶店的排队叫号系统” :
state:代表奶茶店里剩余的“椰果奶茶”数量(资源)-14。
CLH队列:排队等奶茶的小哥哥小姐姐们(线程)-14。
CAS操作:店员用“无接触扫码枪”快速处理订单——谁先刷到谁拿奶茶,保证原子操作-14。
当奶茶卖完时(state=0),新来的顾客自动排到队尾(进入CLH队列),等前面的人买完,店员就会叫下一位(唤醒后继线程)。
核心价值
AQS解决了同步工具实现中 “重复造轮子” 的问题,将线程排队、阻塞、唤醒等通用逻辑抽取出来,让开发者只需关注state的含义和获取/释放资源的逻辑,大大降低了实现自定义锁和同步器的门槛-6。
三、关联概念讲解:state + CLH队列 + 模板方法
state —— 同步状态
state是AQS的核心变量,用volatile int修饰,保证多线程间的可见性-1。它的含义完全由子类定义:
| 同步器 | state的含义 |
|---|---|
| ReentrantLock | 0=无锁,1=被占用,>1=重入次数 |
| Semaphore | 剩余许可数量 |
| CountDownLatch | 倒计时的当前值 |
所有对state的修改必须通过三个原子方法完成:getState()、setState(int)和compareAndSetState(int, int),后者的CAS操作是线程安全的底层保障-3。
CLH队列 —— 等待线程的秩序系统
AQS内部维护一个虚拟的FIFO双向链表(CLH变种) ,核心特点如下:
虚拟双向队列:不存在实际的队列实例,只通过节点间的
prev和next指针维护逻辑结构-1。头节点(head)和尾节点(tail) 都是
volatile的,新线程抢锁失败时被封装成Node加入队尾。head节点不存实际线程,只作哨兵;真正执行的线程从
head.next开始。释放资源后,会唤醒
head.next节点对应的线程,实现“接力式”调度-3。
Node节点的重要状态(waitStatus)
waitStatus是Node内部的状态标记,决定节点的排队和唤醒行为-1-2:
| 常量 | 值 | 含义 |
|---|---|---|
| 0 | 0 | 初始状态,刚入队未挂起 |
| CANCELLED | 1 | 线程被中断或超时,该节点将被跳过,不参与后续唤醒 |
| SIGNAL | -1 | 核心标记:前驱节点释放后需唤醒本节点 |
| CONDITION | -2 | 仅用于Condition队列,表示在条件队列中等待 |
| PROPAGATE | -3 | 共享模式下使用,表示共享传播需继续唤醒后继 |
模板方法 —— 子类定制的入口
AQS本身不实现具体逻辑,只定义了一组protected方法,由子类决定怎么解释state和排队规则-3:
// 独占模式 protected boolean tryAcquire(int arg) // 尝试获取资源 protected boolean tryRelease(int arg) // 尝试释放资源 // 共享模式 protected int tryAcquireShared(int arg) // 尝试获取资源(返回负数失败、0刚好、正数有剩余) protected boolean tryReleaseShared(int arg) // 尝试释放资源 // 条件变量用 protected boolean isHeldExclusively() // 判断当前是否独占持有
四、概念关系与区别总结
三者关系一句话概括:state是目标,队列是手段,模板方法是接口。 它们合起来,让ReentrantLock、Semaphore、CountDownLatch这些看似不同的同步工具,背后共享同一套稳健的调度逻辑-3。
对比总结表
| 组件 | 作用 | 类比 |
|---|---|---|
| state | 描述“还有多少资源” | 奶茶店剩余奶茶数量 |
| CLH队列 | 管理“谁在等、谁该醒” | 排队顾客的长队 |
| 模板方法 | 让子类定义“如何拿/还资源” | 店员决定“先来后到还是熟人插队” |
五、代码示例:基于AQS的ReentrantLock使用
下面是一个完整的ReentrantLock代码示例,展示AQS在独占锁中的实际应用:
import java.util.concurrent.locks.ReentrantLock; public class AQSDemo { private static int counter = 0; private static final ReentrantLock lock = new ReentrantLock(); // 底层基于AQS public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(() -> { // 步骤1:尝试获取锁(最终调用AQS.acquire()) lock.lock(); try { // 步骤2:临界区代码,只有获取到锁的线程能执行 for (int j = 0; j < 1000; j++) { counter++; } } finally { // 步骤3:释放锁(最终调用AQS.release()) lock.unlock(); } }); threads[i].start(); } for (Thread t : threads) { t.join(); } System.out.println("最终计数结果:" + counter); // 预期输出:10000 } }
AQS内部的获取锁流程(以非公平锁为例)
当线程调用lock.lock()时,AQS内部的acquire()方法执行以下核心流程-1:
tryAcquire():由子类实现,尝试直接获取锁。非公平锁会先尝试CAS抢一次。
addWaiter():如果获取失败,将当前线程封装成
Node节点,通过CAS加入队列尾部。acquireQueued():进入自旋循环:
只有前驱节点是头节点时才尝试获取锁;
若失败则调用
LockSupport.park()阻塞当前线程;返回等待过程中是否被中断。
selfInterrupt():若等待过程中被中断,重新设置线程的中断标志位。
AQS内部的释放锁流程
当线程调用lock.unlock()时,AQS内部的release()方法执行以下流程-1:
tryRelease():由子类实现,尝试释放锁。ReentrantLock中将
state减1,减到0时返回true。unparkSuccessor():找到队列中的后继节点,调用
LockSupport.unpark()唤醒它。
关键点:释放锁时如果不唤醒后继节点,队列中的线程将永远阻塞——这正是面试中的高频考点。
六、底层原理与技术支撑
AQS的底层依赖三个核心支柱-2:
1. volatile内存语义
state用volatile修饰,保证所有线程看到的state值都是一致的,禁止指令重排序。
2. CAS(Compare And Swap)原子操作
AQS通过unsafe.compareAndSwapInt()实现CAS,保证对state和队列指针(tail)的修改是原子性的,这是AQS无锁化设计的核心。
3. LockSupport的park/unpark
LockSupport.park()和LockSupport.unpark(Thread)是AQS实现线程阻塞与唤醒的底层工具,基于Unsafe类的park方法实现。与传统synchronized的阻塞机制不同,LockSupport提供了更灵活的线程调度能力。
AQS与synchronized的底层差异:synchronized依赖于JVM的monitor机制(monitorenter/monitorexit指令),在字节码层面实现;而AQS是在Java代码层面实现的同步框架,更加灵活、可控,支持自定义同步策略-。
本文不深入AQS源码细节,AQS的完整源码分析将作为进阶内容在后续文章中展开。
七、高频面试题与参考答案
面试题1:AQS的全称和核心定位是什么?它解决了什么问题?
参考答案:
全称:AbstractQueuedSynchronizer,抽象队列同步器。
核心定位:Java并发包(
java.util.concurrent)中构建锁和同步器的底层框架,ReentrantLock、Semaphore、CountDownLatch都基于它实现。解决的问题:将同步工具开发中重复的线程排队、阻塞、唤醒逻辑抽取出来,开发者只需关注
state的含义和获取/释放资源的逻辑,大大降低了自定义锁和同步器的实现门槛。
踩分点:全称准确 + 框架定位 + 解决的问题(消除重复代码、降低实现门槛)。
面试题2:AQS的核心组成部分有哪些?CLH队列的特点是什么?
参考答案:
三大核心组件:
volatile int state(同步状态)+ CLH虚拟双向队列(管理等待线程)+ 模板方法(供子类定制获取/释放逻辑)。CLH队列的特点:
虚拟双向队列,没有实际队列实例,靠节点间的
prev/next关联。head节点不存实际线程,只作哨兵。
当锁释放时,唤醒
head.next节点对应的线程,实现“接力式”调度。本质是管理线程阻塞与唤醒的秩序系统,不是数据存储容器。
踩分点:三大组件齐 + CLH的“虚拟双向”+“哨兵头节点”+“接力唤醒”。
面试题3:AQS支持的独占锁和共享锁有什么区别?分别对应哪些并发工具?
参考答案:
独占模式:同一时间只有一个线程能获取资源,如
ReentrantLock-1。共享模式:同一时间多个线程能同时获取资源,如
CountDownLatch、Semaphore。核心区别:独占模式下,成功获取资源的线程会独占比资源;共享模式下,多个线程可以同时占有资源,
state代表可用的资源数量而非是否被占用。
踩分点:两种模式的定义 + 典型工具举例 + 核心区别(独占vs共享)。
面试题4:AQS中线程的阻塞与唤醒机制是什么?
参考答案:
阻塞:当线程在
acquireQueued()中自旋获取锁失败且前驱节点状态为SIGNAL时,调用LockSupport.park()阻塞当前线程。唤醒:锁释放时,
unparkSuccessor()找到队列中第一个未被取消的后继节点,调用LockSupport.unpark()唤醒它。核心优势:避免无脑自旋浪费CPU,实现了精确的按需唤醒,而非
synchronized的“全部通知再竞争”。
踩分点:park阻塞 + unpark唤醒 + 避免CPU空转 + 精确唤醒。
面试题5:AQS的公平锁和非公平锁有什么区别?为什么非公平锁性能更高?
参考答案:
公平锁:严格按照FIFO顺序获取锁,新来的线程必须先检查队列中是否有等待线程,有则排队。
非公平锁:新来的线程先尝试CAS抢一次锁,失败再排队。这给了新线程“插队机会”。
性能差异:非公平锁减少了线程挂起和唤醒的开销,在低到中等竞争强度下吞吐量更高。但极端高竞争下,公平锁能避免“饥饿”现象。
踩分点:公平/非公平的核心区别 + 性能差异原因 + 适用场景提示。
八、结尾总结
核心知识点回顾
| 知识点 | 核心要点 |
|---|---|
| AQS定位 | J.U.C中构建锁和同步器的抽象框架 |
| state | volatile int,由子类定义语义,通过CAS原子更新 |
| CLH队列 | 虚拟双向FIFO队列,管理线程的等待与唤醒顺序 |
| 模板方法 | tryAcquire/tryRelease等,子类实现具体语义 |
| 两种模式 | 独占(ReentrantLock)vs 共享(CountDownLatch/Semaphore) |
| 底层支撑 | volatile + CAS + LockSupport |
重点强调
理解AQS的关键不在通读全部源码,而在于抓住 state状态机 + CLH变种队列 + park/unpark 这三根支柱-2。
面试中能自然地说出 “AQS是先CAS抢一次,再决定排不排队” 和 “release的重点不是setState,而是唤醒后继节点” ,往往是面试官判断你是否“真懂”的分水岭-22。
易错点提醒
自定义同步器时,必须在
tryRelease中正确唤醒后继节点,否则后续线程会永远阻塞。共享模式下返回值为正数时要触发传播唤醒,不能只唤醒一个节点。
下一篇预告
本文重点讲解了AQS的核心概念和独占模式。在下一篇中,我们将深入AQS源码级分析,详细拆解acquire()和release()的每一行代码,并手把手带你实现一个自定义的共享锁。敬请期待!
本文由小星AI助手整理输出,数据来源于2026年4月最新的Java技术文档和面试题库。