从源码到实战:吃透 AQS 核心原理,解锁 JUC 同步器底层逻辑
作为 Java 并发编程的核心基石,AQS(AbstractQueuedSynchronizer)是每个大厂程序员必须深入理解的知识点。无论是 ReentrantLock、Semaphore,还是 CountDownLatch,这些我们日常开发中高频使用的同步器,其底层实现都离不开 AQS 的支撑。今天,我将从底层设计思想出发,结合源码拆解,用最通俗易懂的方式带你吃透 AQS—— 不仅让你知道 "是什么",更让你明白 "为什么这么设计" 和 "怎么用"。
一、先搞懂:AQS 到底是什么?
AQS 全称 AbstractQueuedSynchronizer,翻译过来是 "抽象队列同步器"。从名字就能看出它的核心定位:
- 抽象:它是一个抽象类,不直接对外提供服务,而是定义了一套同步器的模板骨架,让子类去实现具体逻辑;
- 队列:内部维护了一个双向链表实现的 FIFO 等待队列,用来存放竞争资源失败的线程;
- 同步器:核心功能是协调多线程对共享资源的访问,解决并发安全问题。
简单说,AQS 就像一个 "并发工具箱",提供了线程排队、资源竞争、唤醒等待等核心能力,JUC 中的各种同步器只需要基于它做少量定制,就能实现自己的同步逻辑。
二、AQS 核心设计:两大基石 + 三大核心方法
AQS 的底层设计非常简洁,核心就围绕 "一个状态变量 + 一个等待队列" 展开,再加上模板方法模式的设计,构成了整个同步器的骨架。
1. 基石一:状态变量 state(资源的 "晴雨表")
AQS 中最核心的成员变量就是state,它是一个 volatile 修饰的 int 类型变量,用来表示共享资源的状态:
// AQS源码核心变量
private volatile int state;
- volatile 修饰:保证 state 的可见性和有序性,多线程环境下能及时感知状态变化;
- 状态含义自定义:state 的具体意义由子类决定,AQS 不做限制,比如:
- ReentrantLock 中:state=0 表示锁未被持有,state>0 表示锁被持有(数值等于重入次数);
- Semaphore 中:state 表示可用的许可数量;
- CountDownLatch 中:state 表示需要等待的计数器值。
AQS 提供了三个原子操作方法来操作 state,保证线程安全:
// 获取当前state值
protected final int getState() {
return state;
}
// 设置state值(不保证原子性,适合单线程操作场景)
protected final void setState(int newState) {
state = newState;
}
// 原子性修改state值(CAS操作,核心方法)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
这三个方法是子类操作资源状态的基础,尤其是compareAndSetState(CAS),是实现无锁竞争的核心。
2. 基石二:双向链表等待队列(线程的 "候车室")
当线程竞争资源失败时,不会立即退出,而是会被封装成一个 Node 节点,加入到 AQS 的等待队列中。这个队列是一个双向链表,结构如下:

注:图片来自美团技术文章,需要了解更多可以看文章末尾的美团技术文章的网址!
- Node 节点核心属性:
static final class Node { // 标记节点类型:共享模式(如Semaphore)、独占模式(如ReentrantLock) static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; // 节点状态:CANCELLED(已取消)、SIGNAL(需唤醒后继节点)等 volatile int waitStatus; // 前驱节点 volatile Node prev; // 后继节点 volatile Node next; // 当前节点对应的线程 volatile Thread thread; } - 队列特点:FIFO(先进先出),保证线程等待的公平性(默认非公平,可通过子类实现公平锁);
- 头节点和尾节点:AQS 维护了两个指针
head(头节点)和tail(尾节点),头节点是当前持有资源的线程节点,其他节点都是等待线程。
3. 三大核心方法(模板方法模式)
AQS 采用模板方法模式,定义了一套同步器的核心流程,子类只需要重写以下三个方法,就能实现自己的同步逻辑:
// 尝试获取独占资源(如ReentrantLock的lock())
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放独占资源(如ReentrantLock的unlock())
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 尝试获取共享资源(如Semaphore的acquire())
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放共享资源(如Semaphore的release())
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 判断当前线程是否持有独占资源(用于重入锁)
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
- 模板方法:AQS 中定义了
acquire()、release()、acquireShared()、releaseShared()等公共方法,这些方法会调用子类重写的tryAcquire()等方法,完成线程排队、唤醒等逻辑; - 子类职责:子类只需要关注 "如何获取资源" 和 "如何释放资源"(即重写上述方法),而不需要关心线程排队、阻塞、唤醒等底层细节 ——AQS 已经帮我们实现好了。
三、AQS 核心流程拆解:以独占锁为例(结合源码)
我们以 ReentrantLock 的独占锁(非公平锁)为例,拆解 AQS 的核心工作流程:线程获取锁、排队等待、释放锁并唤醒下一个线程。
1. 线程获取锁(acquire () 方法)
当线程调用lock()方法时,底层会调用 AQS 的acquire(1)方法(arg=1 表示获取 1 个资源):
// AQS的acquire方法(模板方法)
public final void acquire(int arg) {
// 1. 尝试获取资源(tryAcquire由子类ReentrantLock实现)
// 2. 如果获取失败,将线程封装成Node加入等待队列
// 3. 阻塞当前线程
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
这个方法的核心逻辑可以拆成三步:
第一步:tryAcquire (arg)—— 尝试获取资源
tryAcquire由 ReentrantLock 的非公平锁实现类NonfairSync重写,核心逻辑是 CAS 修改 state:
// ReentrantLock的NonfairSync子类重写tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 情况1:state=0(锁未被持有),CAS尝试将state设为1
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 成功获取锁,记录当前持有锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
// 情况2:当前线程已经持有锁(重入),state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 溢出
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 情况3:锁已被其他线程持有,获取失败
return false;
}
第二步:addWaiter (Node.EXCLUSIVE)—— 加入等待队列
如果tryAcquire返回 false(获取资源失败),会调用addWaiter方法,将当前线程封装成独占模式的 Node 节点,加入等待队列的尾部:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试加入尾部(CAS操作,避免锁)
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速加入失败,通过enq方法循环加入(保证成功)
enq(node);
return node;
}
// 循环CAS加入队列,直到成功
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列为空,初始化头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
第三步:acquireQueued (Node, arg)—— 阻塞线程
节点加入队列后,会调用acquireQueued方法,让线程在队列中等待,直到获取到资源或被中断:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 循环尝试获取资源
for (;;) {
final Node p = node.predecessor(); // 获取前驱节点
// 如果前驱是头节点(说明当前节点是队列第二个节点),尝试获取资源
if (p == head && tryAcquire(arg)) {
setHead(node); // 成功获取资源,将当前节点设为头节点
p.next = null; // 释放前驱节点,帮助GC
failed = false;
return interrupted;
}
// 如果获取失败,判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node); // 获取失败,取消当前节点
}
}
shouldParkAfterFailedAcquire:判断当前线程是否需要阻塞,核心是将前驱节点的状态设为 SIGNAL(表示当前驱节点释放资源后,需要唤醒当前节点);parkAndCheckInterrupt:通过LockSupport.park(this)阻塞当前线程,直到被唤醒或中断。
2. 线程释放锁(release () 方法)
当线程完成业务逻辑后,调用unlock()方法,底层会调用 AQS 的release(1)方法:
// AQS的release方法(模板方法)
public final boolean release(int arg) {
// 1. 尝试释放资源(tryRelease由子类ReentrantLock实现)
if (tryRelease(arg)) {
Node h = head;
// 2. 头节点不为空且状态不是0,唤醒后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
第一步:tryRelease (arg)—— 释放资源
tryRelease由 ReentrantLock 的子类重写,核心逻辑是修改 state 值:
// ReentrantLock的Sync子类重写tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 只有持有锁的线程才能释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state=0表示完全释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 清空持有锁的线程
}
setState(c); // 更新state(重入锁时state>0)
return free;
}
第二步:unparkSuccessor (Node)—— 唤醒后继节点
释放资源成功后,会调用unparkSuccessor方法,唤醒头节点的后继节点(等待队列中的下一个线程):
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 将头节点状态设为0
Node s = node.next;
// 找到状态正常的后继节点(跳过已取消的节点)
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒后继节点的线程
if (s != null)
LockSupport.unpark(s.thread);
}
被唤醒的线程会回到acquireQueued的循环中,再次尝试获取资源。
四、AQS 的两种工作模式:独占式 vs 共享式
AQS 支持两种资源访问模式,对应不同的同步场景:
1. 独占式(Exclusive)
- 特点:同一时刻,只有一个线程能持有资源(锁);
- 典型应用:ReentrantLock(可重入锁);
- 核心方法:
tryAcquire()、tryRelease()。
2. 共享式(Shared)
- 特点:同一时刻,多个线程可以同时持有资源;
- 典型应用:Semaphore(信号量,允许多个线程同时访问)、CountDownLatch(倒计时器,多个线程等待一个线程完成);
- 核心方法:
tryAcquireShared()、tryReleaseShared(); - 返回值含义:
tryAcquireShared返回值 > 0 表示获取资源成功,且还有剩余资源;=0 表示获取成功但无剩余资源;<0 表示获取失败。
五、AQS 实战:手写一个简单的独占锁
理解了 AQS 的核心原理后,我们可以基于 AQS 手写一个简单的独占锁,感受一下子类如何定制同步逻辑:
1. 自定义独占锁实现
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
// 自定义独占锁,基于AQS实现
public class MyLock implements Lock {
// 内部类:基于AQS实现同步逻辑
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取独占资源(state=0表示未锁定,CAS设为1)
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
// 成功获取锁,记录当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
// 重入锁支持:如果当前线程已持有锁,state+1
if (Thread.currentThread() == getExclusiveOwnerThread()) {
setState(getState() + 1);
return true;
}
return false;
}
// 尝试释放独占资源
@Override
protected boolean tryRelease(int arg) {
// 只有持有锁的线程才能释放
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
int newState = getState() - arg;
boolean free = (newState == 0);
if (free) {
setExclusiveOwnerThread(null);
}
setState(newState);
return free;
}
// 判断当前线程是否持有锁
@Override
protected boolean isHeldExclusively() {
return getState() > 0 && Thread.currentThread() == getExclusiveOwnerThread();
}
}
// 持有AQS的实例
private final Sync sync = new Sync();
// 实现Lock接口的方法
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
// 其他Lock接口方法(tryLock、lockInterruptibly等)可类似实现
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public java.util.concurrent.locks.Condition newCondition() {
return null; // 简化实现,实际需基于AQS的ConditionObject
}
}
2. 测试自定义锁
public class MyLockTest {
private static int count = 0;
private static final MyLock lock = new MyLock();
public static void main(String[] args) throws InterruptedException {
// 创建10个线程,每个线程累加1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终count值:" + count); // 输出10000,无并发问题
}
}
通过这个例子可以看出,基于 AQS 实现一个自定义锁非常简单 —— 我们只需要重写tryAcquire、tryRelease等核心方法,就能借助 AQS 的底层能力,实现线程安全的同步逻辑。
六、AQS 核心面试考点总结
- AQS 的核心组成:state 变量(资源状态)+ 双向链表等待队列(线程排队);
- AQS 的设计模式:模板方法模式,父类定义流程,子类实现具体的资源获取 / 释放逻辑;
- AQS 的两种模式:独占式(ReentrantLock)和共享式(Semaphore、CountDownLatch);
- ReentrantLock 的重入原理:通过 state 记录重入次数,
tryAcquire时判断当前线程是否为持有锁的线程,若是则 state+1; - AQS 的线程阻塞 / 唤醒机制:基于 LockSupport.park ()/unpark (),底层依赖 Unsafe 类的 native 方法;
- AQS 的公平锁 vs 非公平锁:
- 公平锁:线程获取锁时,先检查队列是否有等待线程,有则排队;
- 非公平锁:线程获取锁时,先尝试 CAS 获取,失败再排队(ReentrantLock 默认非公平锁,效率更高)。
七、总结
AQS 是 JUC 的灵魂,它的设计思想非常精妙 —— 通过一个 volatile 的 state 变量维护资源状态,用双向链表管理等待线程,再通过模板方法模式将复杂的并发逻辑封装起来,让子类可以轻松实现各种同步器。
理解 AQS 的关键,不在于死记硬背源码,而在于把握 "状态变量 + 等待队列" 的核心设计,以及模板方法模式的运用。掌握了 AQS,你就能看透 ReentrantLock、Semaphore 等同步器的底层逻辑,在遇到并发问题时也能更加从容地分析和解决。
作为大厂程序员,深入理解 AQS 不仅能帮你应对面试,更能让你在设计高并发系统时,借鉴其优秀的设计思想,写出更高效、更安全的并发代码。希望这篇文章能带你真正吃透 AQS,在 Java 并发编程的道路上再进一步!
需要更加深入的可以看来自 美团技术的文章。
更多推荐



所有评论(0)