AQS详解

在 Java 并发编程领域,java.util.concurrent(JUC)包是我们实现高并发、线程安全的核心工具,而AQS(AbstractQueuedSynchronizer,抽象队列同步器) 就是撑起整个 JUC 包的底层骨架。无论是ReentrantLockReentrantReadWriteLock,还是SemaphoreCountDownLatch等并发工具,底层都依赖 AQS 实现。

本文将从AQS 核心原理ReentrantLock 基于 AQS 的实现自旋锁三个维度,带你彻底吃透 AQS,轻松应对面试与实际开发。

一、什么是 AQS?

AQS 是java.util.concurrent.locks包下的抽象类 ,它封装了一套通用的线程同步机制,核心解决三个问题:

  1. 管理同步状态(资源的占用 / 释放标记)
  2. 阻塞 / 唤醒等待的线程
  3. 维护线程等待队列

1.1 AQS 的两大核心组件

AQS 的底层实现非常简洁,仅靠一个状态变量 + 一个同步队列完成所有同步逻辑:

(1)state:同步状态标记

AQS 用一个volatile修饰的int类型变量state表示资源状态,通过 CAS 原子操作修改它,这是线程安全的关键:

  • state=0:资源未被占用(锁空闲)
  • state>0:资源已被占用(锁被持有,重入时 state 递增)

不同同步工具对state的定义不同:

  • ReentrantLockstate=0无锁,state=1独占锁,state>1重入次数
  • CountDownLatchstate=N计数器,state=0放行所有线程

(2)CLH 同步队列

AQS 采用CLH(Craig.Landin.Hagersten)变种双向队列 存储被阻塞的线程,是FIFO(先进先出) 结构:

  • 双向队列:支持队列头尾快速插入、删除节点
  • FIFO 特性:保证等待最久的线程优先获取资源(公平锁核心)
  • 节点:每个节点封装一个等待的线程,包含线程状态、前驱 / 后继节点引用

二、ReentrantLock:AQS 的经典实现

ReentrantLock(可重入锁)是 AQS 最典型的应用,它支持公平锁非公平锁两种模式,底层完全通过 AQS 实现加锁、解锁、重入逻辑。

2.1 公平锁 vs 非公平锁

这是面试高频考点,核心区别在于线程是否能插队获取锁

  • 公平锁:严格按照线程请求顺序分配锁,先到先得,不允许插队
  • 非公平锁 :线程尝试获取锁时,若锁刚好释放,可直接插队获取;若锁被占用,再进入队列等待

非公平锁是ReentrantLock的默认实现,因为插队能减少线程阻塞唤醒的开销,吞吐量更高。

2.2 ReentrantLock 的 AQS 结构

ReentrantLock内部定义了 3 个核心类,全部基于 AQS 扩展:

  1. Sync:抽象同步器,继承 AQS,封装公共的加锁、解锁逻辑
  2. NonfairSync:非公平锁同步器,继承 Sync
  3. FairSync:公平锁同步器,继承 Sync

2.3 加锁源码解析(非公平锁)

非公平锁的加锁流程分为尝试快速获取锁阻塞获取锁两步:

  1. 线程调用lock(),先通过 CAS 尝试将state从 0 改为 1,成功则直接持有锁
  2. 若失败,判断是否是当前线程重入(持有锁的线程再次加锁),state递增,成功返回
  3. 若都失败,调用 AQS 的acquire()方法,将线程加入 CLH 队列阻塞

核心源码简化:

复制代码
// 非公平锁初始尝试加锁
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    // CAS修改state=1,成功则持有锁
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(current);
        return true;
    } 
    // 可重入:当前线程已持有锁,state+1
    else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        setState(c);
        return true;
    }
    return false;
}

2.4 加锁源码解析(公平锁)

公平锁比非公平锁多一步队列校验必须先判断队列中是否有等待线程,没有才能尝试加锁,绝对禁止插队。

核心源码简化:

复制代码
// 公平锁初始尝试加锁
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 关键:先检查队列无等待线程,再CAS加锁
        if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } 
    // 可重入逻辑同上
    else if (getExclusiveOwnerThread() == current) {
        setState(++c);
        return true;
    }
    return false;
}

2.5 解锁源码解析

解锁逻辑是加锁的逆过程,公平锁和非公平锁完全通用

  1. 线程调用unlock()state递减 1
  2. state=0,表示锁完全释放,清空持有线程
  3. 唤醒 CLH 队列中第一个等待的线程

核心源码简化:

复制代码
// 尝试释放锁
protected final boolean tryRelease(int releases) {
    // state-1
    int c = getState() - releases;
    // 非持有锁线程解锁,抛异常
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
    // state=0,锁完全释放
    boolean free = (c == 0);
    if (free)
        setExclusiveOwnerThread(null);
    setState(c);
    return free;
}

// 释放成功,唤醒队列下一个线程
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        signalNext(head); // 唤醒头节点的后继线程
        return true;
    }
    return false;
}

三、自旋锁:AQS 中的高效等待机制

在 AQS 和 CAS 操作中,自旋锁是提升性能的关键设计,我们先搞懂它的核心逻辑。

3.1 什么是自旋锁?

当线程获取锁失败时,不进入阻塞状态 ,而是通过无限循环(自旋) 不断尝试获取锁,直到成功为止。

简单来说:线程不休息,一直循环 "问" 锁是否释放

自旋锁伪代码:

复制代码
// 自旋锁核心逻辑
while(true){
    if(尝试获取锁成功){
        break;
    }
    // 否则继续循环
}

3.2 自旋锁在 AQS 中的应用

  • ReentrantLock:加锁失败后,先自旋尝试 CAS 修改state,多次失败后再入队列阻塞
  • 原子类(AtomicInteger):CAS 失败后,自旋重试,直到修改成功

3.3 自旋锁的优缺点

优点

  • 轻量高效:避免线程阻塞 / 唤醒的内核态切换开销(用户态内完成等待)
  • 适合锁竞争不激烈、锁持有时间短的场景,性能远超阻塞锁

缺点

  • 占用 CPU:自旋时 CPU 一直做无用功,竞争激烈时会浪费大量 CPU 资源
  • 不适合锁持有时间长的场景,会导致系统负载飙升

四、面试核心总结(必背)

  1. AQS 是什么? 抽象队列同步器,JUC 核心,用state状态 + CLH 双向队列实现线程同步。

  2. AQS 核心原理?

    • volatile state标记资源状态,CAS 原子修改
    • CLH FIFO 队列存储阻塞线程
    • 加锁失败入队列阻塞,解锁唤醒队列首线程
  3. 公平锁 vs 非公平锁?

    • 公平锁:排队获取,不插队,线程顺序执行
    • 非公平锁:允许锁释放时插队,吞吐量更高,是默认实现
  4. 可重入锁原理? 持有锁的线程再次加锁,state递增;解锁时state递减,直到state=0才完全释放锁。

  5. 自旋锁优缺点?

    • 优点:避免线程阻塞切换,短锁场景性能高
    • 缺点:长锁 / 高竞争场景浪费 CPU

五、总结

AQS 是 Java 并发编程的底层灵魂 ,它把复杂的线程同步逻辑封装成通用框架,让我们无需手动处理线程阻塞、唤醒、队列管理,只需基于state实现自定义同步工具。

ReentrantLock作为 AQS 的经典实现,通过公平 / 非公平模式适配不同业务场景;自旋锁则是 AQS 性能优化的关键,在短锁场景下大幅提升并发效率。

理解 AQS,不仅能轻松应对并发面试,更能让你在开发中写出更高效、更稳定的并发代码。

相关推荐
zero15972 小时前
Python 8天极速入门笔记(大模型工程师专用):第二篇-Python基础入门(变量、数据类型、print输出)
开发语言·笔记·python
sheji34162 小时前
【开题答辩全过程】以 校园帮系统为例,包含答辩的问题和答案
java·spring boot
koping_wu2 小时前
【Java并发】CompletableFuture详解:常用API和底层原理
java·开发语言·python
填满你的记忆2 小时前
《Java 面试常见题型(2026最新版,背完直接能面)》
java·开发语言
小松加哲2 小时前
# Spring Aware 与 BeanPostProcessor:作用、使用与原理(源码级)
java·后端·spring
人还是要有梦想的2 小时前
QT的基本学习路线
开发语言·qt·学习
小松加哲2 小时前
Spring AOP 代理创建时机深度解析:初始化阶段 vs 三级缓存(源码级)
java·spring·缓存
Mr_Xuhhh2 小时前
LeetCode 热题 100 刷题笔记:从数组到字符串的经典解法(续)
java·数据结构·算法
皙然2 小时前
AQS模型详解:Java并发的核心同步框架(从原理到实战)
java·开发语言·jvm