当前位置: 首页 > news >正文

Java并发核心:你以为AQS很复杂?无非是“两个队列“和“一个状态“

前言

上周面试,面试官问我:“你能说说 Java 的AQS基于管程是如何实现的吗?” 我当时只背了概念,结果当场翻车。回来后我花了 3 天,把 AQS源码啃了一遍,整理出这篇能直接拿去面试的笔记。

1、什么是管程

管程是基于MESA模型实现的,管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量

模型如下图所示:

⚠️提醒
✔️入口等待队列:多线程进入的时排队,只允许一个线程进入管程内部,其他线程等待
✔️条件变量和等待队列:解决线程同步的问题

2、Java 中管程(Monitor)的实现

  • 其一是基于Object监视器(Monitor)机制的内置synchronized同步;
  • 其二是基于抽象队列同步器(AQS)构建的java.util.concurrent.locks.Lock显式锁机制。

一、AQS原理分析

1、什么是AQS

java.util.concurrent 包中的同步器大多构建在一些共同的基础行为之上,例如等待队列、条件队列、独占获取与共享获取等。这些行为被抽象为一个统一的框架——AbstractQueuedSynchronizer(简称 AQS)

AQS 是一个用于实现依赖状态型同步器的抽象同步框架,为构建各种同步机制提供了基础支持。

2、AQS实现方式

  • 一般是通过一个内部类Sync继承 AQS
  • 将同步器所有调用都映射到Sync对应的方法

2.1、举个例子🌰🌰(ReentrantLock)

public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; ...... private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer {} ...... }

3、AQS具备的特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平
  • 可重入
  • 允许中断

二、AQS核心结构

1、AQS核心源码

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { static { try { stateOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state")); headOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head")); tailOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); waitStatusOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("waitStatus")); nextOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("next")); } catch (Exception ex) { throw new Error(ex); } } ..... //链表头节点 private transient volatile Node head; // 链表尾节点 private transient volatile Node tail; //共享变量,使用volatile修饰保证线程可见性 private volatile int state; //获取状态 protected final int getState() { return state; } //设置状态 protected final void setState(int newState) { state = newState; } //CAS操作:将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) //stateOffset:就是定义的state值 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } ..... }
⚠️AQS 的关键实现基于以下两点核心机制:
✔️关键字段的可见性
在 AQS 源码中,用于表示同步状态的state、以及等待队列的头尾指针headtail均被声明为volatile,确保这些状态在多线程之间的立即可见性。
✔️基于 CAS 的无锁竞争设计
获取锁的核心逻辑依赖于对state字段的CAS(Compare-And-Swap)操作,通过这一原子性操作实现无锁化的线程竞争与状态更新,从而避免传统锁机制带来的阻塞开销。

2、AQS两种队列

两种队列结构示意图

2.1、同步等待队列

负责管理在竞争锁失败时进入等待状态的线程,基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制

2.1.1、公平锁

新线程到来时,会先检查同步队列中是否有已在等待的线程。只要有排队者,它就会直接进入队列尾部等待,不会尝试获取锁。这是其“公平”的核心。如果队列为空,它仍会尝试获取锁。

2.1.2、非公平锁

新线程到来时,无论队列是否为空,都会先尝试一次CAS操作去“插队”抢锁。只有抢锁失败后,才会进入队列尾部排队。

⚠️提示
✔️AQS默认采用非公平策略,给予了新线程一次“插队”的机会,旨在减少线程切换频率、提升整体吞吐性能。
✔️线程进入同步队列后,会因无法立即获取锁而发生实际的挂起与唤醒,这一过程涉及内核态切换与上下文恢复,因而存在一定的性能开销

2.2、条件等待队列

与特定条件关联,当线程调用await()时,会释放已持有的锁并进入条件队列等待

当其他线程调用signal()唤醒时,该队列中的线程结点会被移至同步等待队列,重新参与锁的竞争

2.2.1、条件队列源码

条件队列唤醒

public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; /** First node of condition queue. */ private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter; ...... public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); } // 唤醒 private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } /** * 将节点从条件队列转移到同步队列 */ final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; } }

条件队列转换为同步队列

private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
⚠️注意
✔️基于LockSupport.unpark(node.thread)、LockSupport.park(node.thread)实现阻塞和唤醒
✔️条件队列在AQS内部是一个单向链表,节点在被转移到同步队列时,被构建为双向链表节点CLH队列

2.3、区别对比

特性条件等待队列同步等待队列
数据结构单向链表双向链表
连接指针nextWaiterprev, next
主要操作await(尾部添加),signal(头部移除)acquire(尾部添加/自旋),release(头部唤醒/移除)
关键需求简单的 FIFO 等待/唤醒必须支持从任意位置取消节点、状态传播、稳定的遍历
设计目标实现简单,节省资源功能完备,操作高效且安全
⚠️注意
✔️节点从单向的条件队列转移到双向的同步队列时,其数据结构确实发生了改变,这是由两个队列所承担的不同职责和操作需求决定的。
✔️这种设计是AQS既能保持高效,又能支持复杂同步语义(如超时、中断、共享模式)的基础之一。

三、总结

AQS不是对管程的简单模仿,而是用轻量级队列算法在 Java 语言层面重新发明了管程。它将操作系统 / JVM 的管程概念抽象为可组合的 Java 组件,使得开发者能够以极低的成本构建出高性能、高可控的同步工具。

到这里,相信你已经对 AQS 与管程模型的关联有了清晰的理解。如果想进一步搞懂synchronized和 AQS 的本质区别,我专门写了一篇深度对比的文章,欢迎点击查看,我们继续深入探讨。

http://www.jsqmd.com/news/408073/

相关文章:

  • Sora模型的原理和架构初探
  • 盘点环氧树脂固化剂领域:几家技术领先的厂家,石英粉/石墨粉/硅微粉/环氧树脂固化剂,环氧树脂固化剂公司排行榜单 - 品牌推荐师
  • 基于Java的短剧追剧一站式系统方案
  • 2026年2月成都空气治理/甲醛检测/除甲醛/空气检测/甲醛治理行业竞争格局深度分析报告 - 2026年企业推荐榜
  • 突破Netty极限:基于Java 21 FFM API手写高性能网络通信框架
  • 如何通过 C# 实现 PowerPoint 转 HTML 格式 - 完整指南
  • 物联网实训-农作物光强检测系统
  • 舞台上的惊艳只是序章:国产人形机器人硬件成熟,AI自主智能才是下一轮产业竞争核心 - 速递信息
  • 必看!分期乐额度回收风控红线,可可收教你安全操作不踩雷 - 可可收
  • OpenVINO™ C# API 3.2 全新发布,基于 AI 大模型的全栈重构,全面进化!
  • 2026年宜昌游览三峡人家丰富旅游线路推荐:多维度对比评价,解决行程规划与深度体验痛点 - 十大品牌推荐
  • 为什么 Spring 和 IDEA 都不推荐使用 @Autowired 注解??
  • 彻底解决Typora字体渲染难题:CSS描边法完美实现老旧字体加粗
  • 物联网实训-室内人员入侵感知系统
  • 2026重庆有机肥厂家实力榜单 定制化方案适配各类种植 本地服务优选 - 深度智识库
  • 模糊PID控制无刷直流电动机调速的 simulink仿真 BLDCM 模糊控制 直流电机 任何版本
  • 服饰电商效率革命:RPA技术深度落地,客服财务运营全流程提效
  • 2026年诺丁山婚礼艺术中心排名,规模大团队专业度高客户评价佳 - mypinpai
  • GPT-4o最强多模态模型实战
  • JMeter中进行JDBC协议压测实战笔记来喽!
  • 电动清废机使用寿命长的品牌,服务商靠谱推荐及型号选择 - 工业设备
  • 多模态:整合大语言模型与Dall-E-Stable Diffusion API
  • 2026年预糊化淀粉生产厂家公司权威推荐:污水处理药剂的生产厂家、污水处理药剂的生产厂家选择指南 - 优质品牌商家
  • 汽车窗膜贴膜靠谱的品牌和青岛店铺排名推荐 - 工业品网
  • APP中断测试知多少
  • 虚拟电厂系统(VPP) AI 编程分级管控实践指南
  • 盘点2026年福州高性价比房间榻榻米定制工厂 - 工业推荐榜
  • 神马影视 8.8 版 2026 新版:源码系统全面优化
  • 2026年宜昌游览三峡人家丰富旅游线路推荐:横向对比与决策参考,直击时间成本与价值获取痛点 - 十大品牌推荐
  • 酷秒神马9.0版2026最新源码系统 技术解析