AQS(AbstractQueuedSynchronizer)源码深度解析:从CLH队列到ReentrantLock实现

前言

在Java并发编程的世界里,AQS(AbstractQueuedSynchronizer)是一个里程碑式的存在。它是java.util.concurrent包的基石,ReentrantLockCountDownLatchSemaphoreCyclicBarrier等并发工具都是基于AQS构建的。

面试中,AQS是区分"会用并发工具"和"理解并发原理"的分水岭。

本文将深入剖析AQS的核心设计思想,从CLH队列到state状态,再到ReentrantLock的公平与非公平锁实现,带你彻底理解这个并发框架的底层原理。


一、AQS概述:并发框架的基石

1.1 什么是AQS?

AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)中的一个抽象类,它提供了一个基于FIFO队列的同步器框架,可以用来构建锁或其他同步组件。

设计思想

  • 使用一个volatile int state表示同步状态
  • 通过内置的FIFO队列(CLH队列变种)管理线程的等待队列
  • 提供独占模式和共享模式两种资源获取方式

1.2 AQS的核心三要素

要素 作用 实现方式
state 同步状态 volatile int state,通过CAS操作修改
CLH队列 线程等待队列 FIFO双向链表,管理等待线程
CAS操作 原子性保证 Unsafe类的CAS方法

1.3 AQS的两种模式

模式 含义 典型实现
独占模式(Exclusive) 同一时刻只有一个线程能获取资源 ReentrantLock
共享模式(Shared) 多个线程可以同时获取资源 CountDownLatch、Semaphore

1.4 AQS的核心方法

AQS采用了模板方法模式,子类通过重写以下方法来定制同步逻辑:

java 复制代码
public abstract class AbstractQueuedSynchronizer {
    // 需要子类重写的方法
    protected boolean tryAcquire(int arg)        // 独占式尝试获取资源
    protected boolean tryRelease(int arg)        // 独占式尝试释放资源
    protected int tryAcquireShared(int arg)      // 共享式尝试获取资源
    protected boolean tryReleaseShared(int arg)  // 共享式尝试释放资源
    protected boolean isHeldExclusively()        // 判断是否被当前线程独占
    
    // 模板方法(final,子类不能重写)
    public final void acquire(int arg)           // 获取资源入口
    public final boolean release(int arg)        // 释放资源入口
    public final void acquireShared(int arg)     // 共享获取入口
    public final boolean releaseShared(int arg)  // 共享释放入口
}

二、AQS核心实现原理

2.1 state 状态管理

state是AQS的核心状态变量,用volatile修饰保证可见性:

java 复制代码
public abstract class AbstractQueuedSynchronizer {
    // 同步状态,用 volatile 保证可见性
    private volatile int state;
    
    // 获取state
    protected final int getState() {
        return state;
    }
    
    // 设置state(普通写,无原子性保证)
    protected final void setState(int newState) {
        state = newState;
    }
    
    // CAS设置state(原子操作)
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
}

state的含义由子类定义

  • ReentrantLock:state = 0 表示未锁定,state > 0 表示锁定次数(可重入计数)
  • CountDownLatch:state 表示需要等待的线程数(计数器)
  • Semaphore:state 表示剩余许可数量

2.2 CLH队列:线程等待队列

AQS使用CLH锁队列的变种作为线程等待队列。

CLH队列的核心结构
java 复制代码
static final class Node {
    // 节点状态
    volatile int waitStatus;
    
    // 等待状态常量
    static final int CANCELLED = 1;   // 线程已取消
    static final int SIGNAL = -1;     // 后继线程需要被唤醒
    static final int CONDITION = -2;  // 线程在条件队列中等待
    static final int PROPAGATE = -3;  // 共享模式下传播唤醒
    
    // 前驱节点
    volatile Node prev;
    
    // 后继节点
    volatile Node next;
    
    // 当前节点对应的线程
    volatile Thread thread;
    
    // 条件队列的下一个节点
    Node nextWaiter;
}
队列结构图
复制代码
┌─────────────────────────────────────────────────────────┐
│                        AQS                               │
│  ┌─────────────────────────────────────────────────┐    │
│  │                    CLH Queue                      │    │
│  │  ┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐   │    │
│  │  │ Head │◄──►│ Node │◄──►│ Node │◄──►│ Tail │   │    │
│  │  │(dummy)│    │(thread)│    │(thread)│    │(thread)│   │    │
│  │  └──────┘    └──────┘    └──────┘    └──────┘   │    │
│  └─────────────────────────────────────────────────┘    │
│                                                          │
│  state = 0/1/N                                          │
└─────────────────────────────────────────────────────────┘

关键特点

  • FIFO双向链表:保证线程获取资源的公平性
  • 虚拟头节点:Head节点是一个占位节点,不关联具体线程
  • 自旋+CAS:通过自旋+CAS保证入队和出队的线程安全

2.3 独占模式获取与释放流程

acquire() 获取资源
java 复制代码
public final void acquire(int arg) {
    // 1. tryAcquire:尝试获取资源(子类实现)
    // 2. addWaiter:如果失败,将当前线程加入等待队列
    // 3. acquireQueued:在队列中自旋等待获取资源
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
addWaiter() 入队操作
java 复制代码
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 快速尝试:如果队列不为空,CAS将节点加入队尾
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 队列为空或CAS失败,进入完整入队逻辑
    enq(node);
    return node;
}

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() 等待获取
java 复制代码
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;  // 断开旧头节点
                failed = false;
                return interrupted;
            }
            // 检查是否应该阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
release() 释放资源
java 复制代码
public final boolean release(int arg) {
    // 尝试释放资源(子类实现)
    if (tryRelease(arg)) {
        Node h = head;
        // 唤醒后继线程
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 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);  // 唤醒线程
}

三、ReentrantLock 的 AQS 实现

ReentrantLock是AQS在独占模式下的典型实现。它的内部定义了Sync抽象类继承AQS,并提供了公平锁(FairSync)和非公平锁(NonfairSync)两种实现。

3.1 整体架构

java 复制代码
public class ReentrantLock implements Lock {
    private final Sync sync;
    
    // 同步器抽象类
    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();
        
        // 非公平尝试获取锁
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 未锁定时,CAS尝试获取
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                // 可重入:state增加
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        
        // 释放锁
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    }
    
    // 非公平锁
    static final class NonfairSync extends Sync {
        final void lock() {
            // 上来先CAS抢一次
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    // 公平锁
    static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }
        
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 关键:检查队列中是否有等待线程
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
}

3.2 非公平锁实现详解

非公平锁的核心:新来的线程有机会"插队"获取锁,而不必排队等待。

java 复制代码
static final class NonfairSync extends Sync {
    final void lock() {
        // 关键点1:刚进来就直接CAS尝试获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);  // 失败才进入AQS队列
    }
    
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

// Sync中的nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 关键点2:再次尝试CAS获取
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        // 可重入逻辑
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

非公平锁的"插队"体现在

  1. lock()方法一进来就CAS抢锁,不检查队列
  2. tryAcquire中再次CAS抢锁,不检查队列
  3. 只有抢锁失败后,才会乖乖进入队列排队

3.3 公平锁实现详解

公平锁的核心:严格按照FIFO顺序获取锁,不允许插队。

java 复制代码
static final class FairSync extends Sync {
    final void lock() {
        // 直接调用acquire,没有提前CAS
        acquire(1);
    }
    
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 关键点:检查是否有线程在排队等待
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

// 检查是否有线程排队
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    // 队列不为空,且头节点的后继节点不是当前线程
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

公平锁与非公平锁的核心区别

步骤 非公平锁 公平锁
lock() 先CAS抢锁一次 直接acquire
tryAcquire 直接CAS抢锁 先检查是否有排队线程
插队 允许 不允许

3.4 公平锁 vs 非公平锁:性能与场景

特性 非公平锁 公平锁
吞吐量 更高 较低
线程饥饿 可能发生 不会发生
上下文切换 较少 较多
适用场景 高并发、追求吞吐量 避免饥饿、需要公平性

为什么非公平锁性能更高?

  • 减少线程挂起和唤醒的开销
  • 刚释放锁的线程有CPU缓存亲和性,立即获取锁效率高

非公平锁的潜在问题

  • 可能导致线程饥饿(长期等待的线程一直获取不到锁)
  • 但实际场景中,这种概率很低,且可重入锁的特性会缓解饥饿

四、AQS的共享模式:CountDownLatch示例

为了全面理解AQS,我们简要看一个共享模式的实现:CountDownLatch

java 复制代码
public class CountDownLatch {
    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count);
        }
        
        // 共享模式尝试获取
        protected int tryAcquireShared(int acquires) {
            // state为0表示可以获取
            return (getState() == 0) ? 1 : -1;
        }
        
        // 共享模式尝试释放
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
    
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    public void countDown() {
        sync.releaseShared(1);
    }
}

共享模式与独占模式的区别

  • 独占模式:state表示资源是否被占用(0/1或可重入计数)
  • 共享模式:state表示可用资源数量(可被多个线程同时获取)

五、AQS的高级特性

5.1 条件队列(Condition)

AQS通过ConditionObject内部类实现条件队列,配合await()signal()实现线程的精确唤醒。

java 复制代码
public class ConditionObject implements Condition {
    private Node firstWaiter;  // 条件队列头
    private Node lastWaiter;   // 条件队列尾
    
    public final void await() throws InterruptedException {
        // 将当前线程加入条件队列
        Node node = addConditionWaiter();
        // 释放锁
        int savedState = fullyRelease(node);
        // 阻塞等待
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);
        }
        // 重新获取锁
        acquireQueued(node, savedState);
    }
    
    public final void signal() {
        // 将条件队列中的节点转移到同步队列
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }
}

条件队列与同步队列的关系

复制代码
同步队列(CLH)                    条件队列
┌─────┐    ┌─────┐              ┌─────┐    ┌─────┐
│Node1│◄──►│Node2│              │NodeA│◄──►│NodeB│
└─────┘    └─────┘              └─────┘    └─────┘
   ↑          ↑                     ↑          ↑
 等待锁      等待锁             await等待   await等待

signal() 操作:将条件队列的节点转移到同步队列

5.2 中断响应

AQS提供了可中断的获取方法:

java 复制代码
public final void acquireInterruptibly(int arg) 
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

5.3 超时获取

AQS支持超时获取锁:

java 复制代码
public final boolean tryAcquireNanos(int arg, long nanosTimeout) 
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

六、常见面试题解析

Q1:AQS的核心思想是什么?

:AQS的核心思想是将同步状态(state)与等待队列(CLH队列)相结合。通过CAS操作修改state,线程获取资源失败时进入FIFO队列等待,释放资源时唤醒队列中的后继线程。这种设计将复杂的同步逻辑抽象为"尝试获取-排队等待-唤醒"的通用框架。

Q2:CLH队列为什么是双向链表?

:双向链表的设计主要考虑:

  1. 高效的前驱访问:节点需要获取前驱节点的状态(waitStatus)
  2. 取消节点的清理:当节点被取消时,需要从队列中移除,双向链表便于操作
  3. 从尾部向前查找unparkSuccessor需要从尾部向前查找第一个有效节点

Q3:公平锁和非公平锁的区别是什么?

  • 公平锁:获取锁之前先检查等待队列中是否有线程在排队,如果有则排队等待,严格按FIFO顺序获取
  • 非公平锁:获取锁时直接CAS尝试,不管队列中是否有等待线程,允许插队

性能上:非公平锁吞吐量更高,但可能导致线程饥饿;公平锁公平性好,但上下文切换开销大。

Q4:ReentrantLock的可重入性是如何实现的?

:通过state状态变量实现。state表示锁的持有次数:

  • 第一次获取锁:state = 1
  • 同一个线程再次获取:state + 1
  • 释放锁:state - 1
  • state = 0时,锁完全释放,其他线程可以获取

Q5:AQS为什么使用int类型的state,而不是boolean?

  1. 支持可重入锁:需要计数功能
  2. 支持共享模式:如Semaphore需要表示剩余许可数量
  3. 扩展性:子类可以根据需要定义state的含义

七、总结

AQS核心架构图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        AbstractQueuedSynchronizer               │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    volatile int state                     │   │
│  │                    (同步状态,由子类定义含义)              │   │
│  └──────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                     CLH Queue (FIFO)                      │   │
│  │  ┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐          │   │
│  │  │ Head │◄──►│ Node │◄──►│ Node │◄──►│ Tail │          │   │
│  │  │(dummy)│    │thread│    │thread│    │thread│          │   │
│  │  └──────┘    └──────┘    └──────┘    └──────┘          │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    模板方法                               │   │
│  │  acquire()    release()    acquireShared()    releaseShared() │
│  └──────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    子类实现                               │   │
│  │  tryAcquire()    tryRelease()    tryAcquireShared()      │   │
│  │  tryReleaseShared()    isHeldExclusively()              │   │
│  └──────────────────────────────────────────────────────────┘   │
│                              │                                   │
│         ┌────────────────────┼────────────────────┐             │
│         ▼                    ▼                    ▼             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │ReentrantLock│    │CountDownLatch│    │ Semaphore   │         │
│  │  (独占模式)  │    │  (共享模式)  │    │  (共享模式)  │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
└─────────────────────────────────────────────────────────────────┘

关键要点回顾

知识点 核心内容
state状态 volatile int state,通过CAS修改,子类自定义含义
CLH队列 FIFO双向链表,管理等待线程
独占模式 同一时刻只有一个线程能获取资源
共享模式 多个线程可以同时获取资源
公平锁 检查队列,不允许插队
非公平锁 直接CAS,允许插队
可重入性 state计数实现
条件队列 ConditionObject实现精确唤醒

学习建议

  1. 源码阅读 :建议跟着本文的源码片段,自己动手追踪一遍AQS的acquirerelease流程
  2. 调试实践:写一个简单的多线程程序,断点调试观察CLH队列的变化
  3. 对比学习 :对比公平锁和非公平锁的tryAcquire实现差异

AQS是理解Java并发包的钥匙,掌握它,你就掌握了JUC的灵魂。


📌 系列回顾

下一篇预告:JUC并发容器深度解析------ConcurrentHashMap、CopyOnWriteArrayList源码分析

希望这篇文章能帮助你在面试中轻松应对AQS相关的问题!如有疑问,欢迎在评论区交流讨论。

相关推荐
江湖中的阿龙2 小时前
深入理解 CAS:Java 无锁并发核心原理、缺陷与应用场景详解
java·开发语言
xianjian09122 小时前
Java进阶-在Ubuntu上部署SpringBoot应用
java·spring boot·ubuntu
拾荒的小海螺2 小时前
JAVA:Spring Boot3 集成 Spring AI 实现 Prompt 提示词工程
java·spring boot·spring
小旭95272 小时前
SpringBoot 整合 MyBatis 与自动配置原理详解
java·spring boot·后端·spring·intellij-idea·mybatis
恼书:-(空寄2 小时前
Seata TCC 生产级(空回滚+悬挂+幂等)+ AT/TCC 混合使用
java·seata·分布式事务
超级无敌大好人2 小时前
程序运行卡住排查
java·spring ai·qdrant
NGC_66112 小时前
深入解析 ConcurrentHashMap 设计思想:高并发下的线程安全哈希表
java·开发语言
无极低码2 小时前
纯Java、无任何第三方依赖、直接可用的 SQLite 工具类
java·jvm·sqlite
weixin_425023002 小时前
Spring Boot 2.7 + JDK 8 实现 WebSocket 集群分布式部署(基于 Redis Pub/Sub 方案)
java·spring boot·websocket