我们来说一说什么是 AQS ?

简介: 我是小假 期待与你的下一次相遇 ~


一、AQS 是什么?

AQS,全称 AbstractQueuedSynchronizer,即抽象队列同步器

  • 抽象:它是一个抽象类,本身不能直接实例化,需要子类去继承它,并实现其保护方法来管理同步状态。
  • 队列:它内部维护了一个先进先出(FIFO)的等待队列,用于存放那些没有抢到锁的线程。
  • 同步器:它是构建锁和其他同步组件(如 Semaphore、CountDownLatch 等)的基础框架

核心思想:

AQS 使用一个整型的 volatile 变量(state 来表示同步状态(例如,锁被重入的次数、许可的数量等),并通过一个内置的 FIFO 队列来完成资源获取线程的排队工作。

设计模式:

AQS 是 模板方法模式 的经典应用。父类(AQS)定义了骨架和核心算法,而将一些关键的操作以 protected 方法的形式留给子类去实现。这样,实现一个自定义同步器只需要关注如何管理 state 状态即可,至于线程的排队、等待、唤醒等复杂操作,AQS 已经帮我们完成了。


二、AQS 的核心结构

AQS 的核心可以概括为三部分:同步状态(state)等待队列条件队列

1. 同步状态(State)

这是一个 volatile int 类型的变量,是 AQS 的灵魂。

private volatile int state;

image.gif

它的具体含义由子类决定,非常灵活:

  • ReentrantLock 中,state 表示锁被同一个线程重复获取的次数。state=0 表示锁空闲,state=1 表示锁被占用,state>1 表示锁被重入。
  • Semaphore 中,state 表示当前可用的许可数量。
  • CountDownLatch 中,state 表示计数器当前的值。

state 的操作是原子的,通过 getState(), setState(int newState), compareAndSetState(int expect, int update) 等方法进行。

2. 等待队列(CLH 队列的变体)

这是一个双向链表,是 AQS 实现阻塞锁的关键。当线程请求共享资源失败时,AQS 会将当前线程以及等待状态等信息构造成一个节点(Node) 并将其加入队列的尾部,同时阻塞该线程。

  • 头节点(Head):指向获取到资源的线程所在的节点。头节点不持有线程,是一个“虚节点”。
  • 尾节点(Tail):指向队列中最后一个节点。

当一个线程释放资源时,它会唤醒后继节点,后继节点成功获取资源后,会将自己设置为新的头节点。

主要原理图如下:

image.gif

AQS 使用一个 Volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作,通过 CAS 完成对 State 值的修改。

3. 条件队列(Condition Object)

AQS 内部类 ConditionObject 实现了 Condition 接口,用于支持 await/signal 模式的线程间协作。每个 ConditionObject 对象都维护了一个自己的单向链表(条件队列)

  • 当线程调用 Condition.await() 时,会释放锁,并将当前线程构造成节点加入条件队列,然后阻塞。
  • 当线程调用 Condition.signal() 时,会将条件队列中的第一个等待节点转移到 AQS 的等待队列中,等待重新获取锁。

注意:一个 AQS 实例可以对应多个 Condition 对象(即多个条件队列),但只有一个等待队列。


三、AQS 的设计与关键方法

AQS 将资源获取的方式分为两种:

  1. 独占模式(Exclusive):一次只有一个线程能执行,如 ReentrantLock。
  2. 共享模式(Shared):多个线程可以同时执行,如 Semaphore、CountDownLatch。

AQS 提供了顶层的入队和出队逻辑,而将尝试获取资源尝试释放资源的具体策略留给了子类。

需要子类重写的关键方法(Protected)

这些方法在 AQS 中是 protected 的,默认抛出 UnsupportedOperationException

独占模式:

  • boolean tryAcquire(int arg):尝试以独占方式获取资源。成功返回 true,失败返回 false。
  • boolean tryRelease(int arg):尝试以独占方式释放资源。成功返回 true,失败返回 false。

共享模式:

  • int tryAcquireShared(int arg):尝试以共享方式获取资源。负数表示失败;0 表示成功,但后续共享获取可能失败;正数表示成功,且后续共享获取可能成功。
  • boolean tryReleaseShared(int arg):尝试以共享方式释放资源。

其他:

  • boolean isHeldExclusively():当前同步器是否在独占模式下被线程占用。在 Condition 相关操作中会用到。

供外部调用的重要方法(Public)

这些是模板方法,子类一般不重写,使用者(或子类)直接调用。

独占模式:

  • void acquire(int arg):以独占模式获取资源,忽略中断。如果获取失败,会进入等待队列。
  • void acquireInterruptibly(int arg):同上,但响应中断。
  • boolean tryAcquireNanos(int arg, long nanosTimeout):在 acquireInterruptibly 基础上增加了超时限制。
  • boolean release(int arg):以独占模式释放资源。

共享模式:

  • void acquireShared(int arg):以共享模式获取资源。
  • void acquireSharedInterruptibly(int arg):响应中断的共享获取。
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout):带超时的共享获取。
  • boolean releaseShared(int arg):以共享模式释放资源。

四、源码级工作流程解析(以 acquire 为例)

我们来看一下最核心的 acquire 方法,它展示了 AQS 的完整工作流程:

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 1. 尝试直接获取资源(子类实现)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 2. 获取失败,则加入队列;3. 在队列中自旋/阻塞等待
        selfInterrupt(); // 如果在等待过程中被中断,补上中断标记
}

image.gif

  1. tryAcquire(arg)
  • 这是子类实现的方法。比如在 ReentrantLock 的非公平锁实现中,它会直接尝试使用 CAS 修改 state,如果成功,就将当前线程设置为独占线程。
  • 如果 tryAcquire 成功,整个 acquire 方法就结束了,线程继续执行。
  • 如果失败,进入下一步。
  1. addWaiter(Node.EXCLUSIVE)
  • 创建一个代表当前线程的 Node 节点,模式为独占模式(Node.EXCLUSIVE)
  • 通过 CAS 操作,快速地将这个新节点设置为尾节点。如果失败,则进入 enq(node) 方法,通过自旋 CAS 的方式确保节点被成功添加到队列尾部。
  1. acquireQueued(final Node node, int arg)
  • 这是核心中的核心。节点入队后,会在这个方法里进行自旋(循环)等待。
  • 在循环中,它会检查自己的前驱节点是不是头节点(p == head)。如果是,说明自己是队列中第一个等待的线程,会再次调用 tryAcquire 尝试获取资源(因为此时锁可能刚好被释放了,这是一个避免不必要的线程挂起、提高性能的优化)。
  • 如果获取成功,就将自己设为新的头节点,然后返回。
  • 如果前驱不是头节点,或者再次尝试获取失败,则会调用 shouldParkAfterFailedAcquire 方法,检查并更新前驱节点的状态(比如将其 waitStatus 设置为 SIGNAL,表示“当你释放锁时,需要唤醒我”)。
  • 如果一切就绪,就调用 parkAndCheckInterrupt() 方法,使用 LockSupport.park(this) 阻塞(挂起)当前线程
  • 当线程被唤醒后(通常是由前驱节点释放锁时 unpark 的),会再次检查自己是否是头节点的后继,并重复上述自旋过程,直到成功获取资源。
  1. selfInterrupt()
  • 如果在等待过程中线程被中断,acquireQueued 方法会返回 true,这里会调用 selfInterrupt 补上中断标志,因为 AQS 在 acquire 过程中是忽略中断的。

释放流程(release)相对简单:

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 1. 子类尝试释放资源
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 2. 唤醒后继节点
        return true;
    }
    return false;
}

image.gif

unparkSuccessor 会找到队列中第一个需要唤醒的线程(通常是头节点的下一个有效节点),然后调用 LockSupport.unpark(s.thread) 将其唤醒。


五、AQS 的应用举例

AQS 是 JUC 包的基石,几乎所有的同步工具都基于它:

  • ReentrantLock:使用 AQS 的独占模式,state 表示重入次数。
  • ReentrantReadWriteLock:读写锁。AQS 的 state 高16位表示读锁状态,低16位表示写锁状态。
  • Semaphore:使用 AQS 的共享模式,state 表示可用许可数。
  • CountDownLatch:使用 AQS 的共享模式,state 表示计数器值。countDown()releaseSharedawait()acquireShared
  • ThreadPoolExecutor:其内部的工作线程 Worker 类,也继承了 AQS,用于实现独占锁,来判断线程是否空闲。

六、总结

AQS 的核心贡献在于,它提供了一个强大的框架,将复杂的线程排队、阻塞、唤醒等底层操作封装起来,让同步器的开发者只需要关注一个核心问题:如何管理那个 state 变量。

它的优点:

  1. 极大地降低了构建锁和同步器的复杂度
  2. 性能高效:通过自旋、CAS 等无锁编程技术,减少了线程上下文切换的开销。
  3. 灵活强大:通过两种模式的区分,可以构建出各种复杂的同步工具。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

image.gif

相关文章
|
15天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
10天前
|
缓存 并行计算 PyTorch
144_推理时延优化:Profiling与瓶颈分析 - 使用PyTorch Profiler诊断推理延迟,优化矩阵运算的独特瓶颈
在2025年的大模型时代,推理时延优化已经成为部署LLM服务的关键挑战之一。随着模型规模的不断扩大(从数亿参数到数千亿甚至万亿参数),即使在最先进的硬件上,推理延迟也常常成为用户体验和系统吞吐量的主要瓶颈。
349 147
|
10天前
|
机器学习/深度学习 存储 缓存
92_自我反思提示:输出迭代优化
在大型语言模型(LLM)应用日益普及的今天,如何持续提升模型输出质量成为了业界关注的核心问题。传统的提示工程方法往往依赖一次性输入输出,难以应对复杂任务中的多轮优化需求。2025年,自我反思提示技术(Self-Reflection Prompting)作为提示工程的前沿方向,正在改变我们与LLM交互的方式。这项技术通过模拟人类的自我反思认知过程,让模型能够对自身输出进行评估、反馈和优化,从而实现输出质量的持续提升。
400 136
|
4天前
|
人工智能 移动开发 自然语言处理
阿里云百炼产品月刊【2025年9月】
本月通义千问模型大升级,新增多模态、语音、视频生成等高性能模型,支持图文理解、端到端视频生成。官网改版上线全新体验中心,推出高代码应用与智能体多模态知识融合,RAG能力增强,助力企业高效部署AI应用。
255 1
|
14天前
|
存储 人工智能 搜索推荐
终身学习型智能体
当前人工智能前沿研究的一个重要方向:构建能够自主学习、调用工具、积累经验的小型智能体(Agent)。 我们可以称这种系统为“终身学习型智能体”或“自适应认知代理”。它的设计理念就是: 不靠庞大的内置知识取胜,而是依靠高效的推理能力 + 动态获取知识的能力 + 经验积累机制。
405 135
|
14天前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
本文讲解 Prompt 基本概念与 10 个优化技巧,结合学术分析 AI 应用的需求分析、设计方案,介绍 Spring AI 中 ChatClient 及 Advisors 的使用。
531 133
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
|
14天前
|
人工智能 Java API
AI 超级智能体全栈项目阶段一:AI大模型概述、选型、项目初始化以及基于阿里云灵积模型 Qwen-Plus实现模型接入四种方式(SDK/HTTP/SpringAI/langchain4j)
本文介绍AI大模型的核心概念、分类及开发者学习路径,重点讲解如何选择与接入大模型。项目基于Spring Boot,使用阿里云灵积模型(Qwen-Plus),对比SDK、HTTP、Spring AI和LangChain4j四种接入方式,助力开发者高效构建AI应用。
545 122
AI 超级智能体全栈项目阶段一:AI大模型概述、选型、项目初始化以及基于阿里云灵积模型 Qwen-Plus实现模型接入四种方式(SDK/HTTP/SpringAI/langchain4j)