作为 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 实现一个自定义锁非常简单 —— 我们只需要重写tryAcquiretryRelease等核心方法,就能借助 AQS 的底层能力,实现线程安全的同步逻辑。

六、AQS 核心面试考点总结

  1. AQS 的核心组成:state 变量(资源状态)+ 双向链表等待队列(线程排队);
  2. AQS 的设计模式:模板方法模式,父类定义流程,子类实现具体的资源获取 / 释放逻辑;
  3. AQS 的两种模式:独占式(ReentrantLock)和共享式(Semaphore、CountDownLatch);
  4. ReentrantLock 的重入原理:通过 state 记录重入次数,tryAcquire时判断当前线程是否为持有锁的线程,若是则 state+1;
  5. AQS 的线程阻塞 / 唤醒机制:基于 LockSupport.park ()/unpark (),底层依赖 Unsafe 类的 native 方法;
  6. AQS 的公平锁 vs 非公平锁
    • 公平锁:线程获取锁时,先检查队列是否有等待线程,有则排队;
    • 非公平锁:线程获取锁时,先尝试 CAS 获取,失败再排队(ReentrantLock 默认非公平锁,效率更高)。

七、总结

AQS 是 JUC 的灵魂,它的设计思想非常精妙 —— 通过一个 volatile 的 state 变量维护资源状态,用双向链表管理等待线程,再通过模板方法模式将复杂的并发逻辑封装起来,让子类可以轻松实现各种同步器。

理解 AQS 的关键,不在于死记硬背源码,而在于把握 "状态变量 + 等待队列" 的核心设计,以及模板方法模式的运用。掌握了 AQS,你就能看透 ReentrantLock、Semaphore 等同步器的底层逻辑,在遇到并发问题时也能更加从容地分析和解决。

作为大厂程序员,深入理解 AQS 不仅能帮你应对面试,更能让你在设计高并发系统时,借鉴其优秀的设计思想,写出更高效、更安全的并发代码。希望这篇文章能带你真正吃透 AQS,在 Java 并发编程的道路上再进一步!

需要更加深入的可以看来自 美团技术的文章

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐