为了带你精通 Java AQS,我画了 40 张图,从管程模型讲起!

操作系统原理,使用管程封装操作系统共享资源,hoare管程模型与mesa管程模型 O、进程中信号量机制的不足:程序编写困难、容易出错 引入管程,管程也是一种进程间的高级同步机制。一、管程定义:有一个明确定义的操作集合,通过它且只能通过它才能操作某共享数据类型的实例。分析: 1、这里管程这个机制维护了共享资源的数据结构和操作过程,等价于c++中将数据结构和函数封装为一体的类 2、参考同一进程中的多个线程可以共享数据,往底层看,操作系统和进程的关系就像进程和进程内线程的关系,操作系统的视角中共享资源等价于进程中的全局变量。二、进程和管程的调用关系: 进程只能通过管程中的过程(函数)来间接的访 阅读详情

Java中 AQS 是 AbstractQueuedSynchronizer 类,AQS 依赖 FIFO 队列来提供一个框架,这个框架用于实现锁以及锁相关的同步器,比如信号量、事件等。

在 AQS 中,主要有两部分功能,一部分是操作 state 变量,第二部分是实现排队和阻塞机制。

注意,AQS 并没有实现任何同步接口,它只是提供了类似 acquireInterruptible 的方法,调用这些方法可以实现锁和同步器。

1 管程模型

Java 使用 MESA 管程模型来管理类的成员变量和方法,让这个类的成员变量和方法的操作是线程安全的。下图是 MESA 管程模型,里面除了定义共享变量外,还定义了条件变量和条件变量等待队列:

上图中有三个知识点:

  • MESA 管程模型封装了共享变量和对共享变量的操作,线程要进入管程内部,必须获取到锁,如果获取锁失败就进入入口等待队列阻塞等待。

  • 如果线程获取到锁,就进入到管程内部。但是进入到管程内部,也不一定能立刻操作共享变量,而是要看条件变量是否满足,如果不满足,只能进入条件变量等待队列阻塞等待。

  • 在条件变量等待队列中,如果被其他线程唤醒,也不一定能立刻操作共享变量,而是需要去入口等待队列重新排队等待获取锁。

Java 中的 MESA 管程模型有一点改进,就是管程内部只有一个条件变量和一个等待队列。下图是 AQS 的管程模型:

AQS 的管程模型依赖 AQS 中的 FIFO 队列实现入口等待队列,要进入管程内部,就由各种并发锁的限制。而 ConditionObject 则实现了条件队列,这个队列可以创建多个。

下面就从入口等待队列、并发锁、条件等待队列三个方面来带你彻底理解 AQS。

2 入口等待队列

2.1 获取独占锁

独占, 忽略 interrupts

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

这里的 tryAcquire 是抽象方法,由 AQS 的子类来实现,因为每个子类实现的锁是不一样的。

2.1.1 入队

上面的代码可以看到,获取锁失败后,会先执行 addWaiter 方法加入队列,然后执行 acquireQueued 方法自旋地获取锁直到成功。

addWaiter 代码逻辑如下图,简单说就是把 node 入队,入队后返回 node 参数给 acquireQueued 方法:

这里有一个点需要注意,如果队列为空,则新建一个 Node 作为队头。

2.1.2 入队后获取锁

acquireQueued 自旋获取锁逻辑如下图:

这里有几个细节:

1.waitStatus

  • CANCELLED(1):当前节点取消获取锁。当等待超时或被中断(响应中断),会触发变更为此状态,进入该状态后节点状态不再变化;

  • SIGNAL(-1):后面节点等待当前节点唤醒;

  • CONDITION(-2):Condition 中使用,当前线程阻塞在 Condition,如果其他线程调用了 Condition 的 signal 方法,这个结点将从等待队列转移到同步队列队尾,等待获取同步锁;

  • PROPAGATE(-3):共享模式,前置节点唤醒后面节点后,唤醒操作无条件传播下去;

  • 0:中间状态,当前节点后面的节点已经唤醒,但是当前节点线程还没有执行完成。

2.获取锁失败后挂起

如果前置节点不是头节点,或者前置节点是头节点但当前节点获取锁失败,这时当前节点需要挂起,分三种情况:

  • 前置节点 waitStatus=-1,如下图:

  • 前置节点 waitStatus > 0,如下图:

  • 前置节点 waitStatus < 0 但不等于 -1,如下图:

3.取消获取锁

如果获取锁抛出异常,则取消获取锁,如果当前节点是 tail 节点,分两种情况如下图:

如果当前节点不是 tail 节点,也分两种情况,如下图:

4.对中断状态忽略

5.如果前置节点的状态是 0 或 PROPAGATE,会被当前节点自旋过程中更新成 -1,以便之后通知当前节点。

2.1.3 独占 + 响应中断

对应方法 acquireInterruptibly(int arg)。

跟忽略中断(acquire方法)不同的是要响应中断,下面两个地方响应中断:

  • 获取锁之前会检查当前线程是否中断。

  • 获取锁失败入队,在队列中自旋获取锁的过程中也会检查当前线程是否中断。如果检查到当前线程已经中断,则抛出 InterruptedException,当前线程退出。

2.1.4 独占 + 响应中断 + 考虑超时

对应方法 tryAcquireNanos(int arg, long nanosTimeout)。

这个方法具备了独占 + 响应中断 + 超时的功能,下面2个地方要判断是否超时:

  • 自旋获取锁的过程中每次获取锁失败都要判断是否超时;

  • 获取锁失败 park 之前要判断超时时间是否大于自旋的阈值时间 (spinForTimeoutThreshold = 1ns) 另外,park 线程的操作使用 parkNanos 传入阻塞时间。

2.2 释放独占锁

独占锁释放分两步:释放锁,唤醒后继节点。

释放锁的方法 tryRelease 是抽象的,由子类去实现。

我们看一下唤醒后继节点的逻辑,首先需要满足两个条件:

  • head 节点不等于 null;

  • head 节点 waitStatus 不等于 0。这里有两种情况(在方法 unparkSuccessor):

  • 情况一,后继节点 waitStatus <= 0,直接唤醒后继节点,如下图:

  • 情况二:后继节点为空或者 waitStatus > 0,从后往前查找最接近当前节点的节点进行唤醒,如下图:

2.3 获取共享锁

之前我们讲了独占锁,这一小节我们谈共享锁,有什么不同呢?

2.3.1 共享,忽略 interrupts

对应方法 acquireShared,代码如下:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

2.3.2 tryAcquireShared

这里获取锁使用的方法是 tryAcquireShared,获取的是共享锁。获取共享锁跟获取独占锁不同的是,会返回一个整数值,说明如下:

  • 返回负数:获取锁失败。

  • 返回 0:获取锁成功但是之后再由线程来获取共享锁时就会失败。

  • 返回正数:获取锁成功而且之后再有线程来获取共享锁时也可能会成功。所以需要把唤醒操作传播下去。tryAcquireShared 获取锁失败后(返回负数),就需要入队后自旋获取,也就是执行方法 doAcquireShared。

2.3.3 doAcquireShared

怎么判断队列中等待节点是在等待共享锁呢?nextWaiter == SHARED,这个参数值是入队新建节点的时候构造函数传入的。

自旋过程中,如果获取锁成功(返回正数),首先把自己设置成新的 head 节点,然后把通知传播下去。如下图:

之后会唤醒后面节点并保证唤醒操作可以传播下去。但是需要满足四个条件中的一个:

  • tryAcquireShared 返回值大于0,有多余的锁,可以继续唤醒后继节点。

  • 旧的 head 节点 waitStatus < 0,应该是其他线程释放共享锁过程中把它的状态更新成了 -3。

JUC并发编程知识地图:从AQS到线程池的系统学习目录 在Java并发编程中,数据共享安全、任务调度与异步协作是长期高频问题。其底层往往绕不开JMM的可见性与有序性、CAS的原子更新,以及AQS的state与CLH队列抽象。理解这些机制后,锁、原子类、并发容器、阻塞队列和线程池不再是孤立API,而能形成可定位、可组合的工程工具。实际场景中,锁竞争、线程池队列积压、计数丢失和异步任务编排失败,都需要顺着基础原理回溯。围绕JUC知识地图,从线程基础与内存模型出发,经锁与AQS、CAS与原子类,再到并发容器、阻塞队列、线程池和CompletableFuture编排,逐 阅读详情

相关推荐

Java并发编程之AQS

AQS是AbstractQueueSynchronizer是缩写,其意思就是抽象队列同步器。原理就是通过一个FIFO队列维护一个state同步状态值,只需要继承该抽象类重写对应的方法便可实现一套线程同步机制。AQS抽象类在java.util.concurrent.locks包下定义。如上图所示,AQS属于一个抽象类,主要是用来构建锁结构以及同步器的一个抽象类。AQS继承了AbstractOwnableSynchronizer,其作用为设置和获取独占锁的拥有者线程方法。

天天新知 2379

深入剖析AQS:JDK层面管程实现

深入剖析AQS:JDK层面管程实现管程是并发编程中的重要概念,用于保护共享资源的访问。在Java中,AbstractQueuedSynchronizer(AQS)是一种基于管程的实现,提供了强大的工具和框架,用于构建各种并发数据结构和同步工具。本文将深入剖析AQS的实现原理,并通过相关源代码来解释其工作机制。

HackLogic的博客 147

将pytorch的权值矩阵量化为定点数补码并导入FPGA中

Xilinx FPGA实现LSTM算法时对ROM的初始化 将pytorch框架的权重矩阵以定点数补码的形式导入到FPGA中 文章目录Xilinx FPGA实现LSTM算法时对ROM的初始化1. pytorch框架中的LSTM2. 初始化一个RNN模型 3. 初始化ROM的coe文件格式 4. 将参数转换为补码,并且输出到coe文件 5. 配置一个ROM 6. 结论7. 代码 1. pytorch框架中的LSTM pytorch框架将LSTM算法中的八个权值矩阵连接成两个矩阵,假设要导入的LSTM网络的输

随缘分享 4527

Qt Design Studio 入门指南:从零开始打造炫酷UI(详细+实用+轻松上手)

本文是Qt Design Studio的详细入门指南,旨在帮助UI设计新手从零开始轻松打造炫酷界面。文章系统介绍了软件的核心概念、安装步骤、主界面解析,并通过创建一个完整的登录界面实例,手把手教学如何拖拽组件、设置属性、添加交互状态与动画。同时,分享了创建自定义组件、管理资源以及提升效率的实用技巧与常见问题解决方案,让读者能够快速上手并应用于实际项目开发。

devops8pract的博客 588

告别信号毛刺!手把手教你用ADS和IBIS-AMI模型搞定PCIe Gen3眼图仿真

本文详细介绍了如何使用Keysight ADS和IBIS-AMI模型进行PCIe Gen3眼图仿真,解决高速信号传输中的信号质量问题。从仿真环境搭建、S参数预处理到眼图优化与故障排除,提供了一套完整的工程实践指南,帮助工程师提升PCIe Gen3接口的信号完整性。

weixin_28366053的博客 362

用STM32F103C8T6和TB6612驱动模块,从零搭建一辆能避障循迹的智能小车(附完整代码)

本文详细介绍了如何使用STM32F103C8T6和TB6612驱动模块从零搭建一辆能避障循迹的智能小车。内容涵盖硬件选型、电路设计、底层驱动配置以及智能行为算法实现,并附有完整代码示例,帮助开发者快速掌握嵌入式开发的核心技术。

weixin_42641741的博客 444

C++之2048小游戏 第二期

游戏会有初始化、绘制界面、处理输入、更新游戏状态、判断胜负等部分。所以,可能需要一个二维数组来表示4x4的网格,存储每个格子的数值。然后,需要处理键盘输入,尤其是方向键的控制,这可能需要使用EasyX的键盘事件处理函数。然后,关于图形绘制部分,每个格子的大小、颜色、数字的显示都需要设计。不同的数字对应不同的颜色,比如2是浅色,4稍深,以此类推。可能需要一个函数根据数字的值返回对应的颜色,然后在绘制时调用。接下来,处理数字的移动和合并逻辑。这是比较复杂的部分。

Lucky_Glory的博客 804

【全志V821_FoxPi】6-3 GC2083 MIPI摄像头适配

本文介绍了在全志V821芯片上适配GC2083 MIPI摄像头的方案。GC2083是一款支持1080P@30fps的高性能CMOS图像传感器,采用MIPI CSI-2接口,具有低功耗特性。文章详细列出了传感器的技术参数和工作电压要求,并提供了设备树配置示例,包括CSI接口、ISP处理单元和传感器节点的具体设置。通过快速配置命令one_gc2083_sensor或手动修改设备树即可启用该摄像头模组,适用于IPC等对图像质量有要求的嵌入式应用场景。

第四维度的博客 1917

基于YOLOv11的手术器械计数系统:从数据集构建到Web应用部署

m2cai16-tool数据集:包含15种腹腔镜手术器械的标注图像Cholec80数据集:包含80个胆囊切除手术视频,标注了7种器械SurgicalTools数据集:包含超过10,000张手术器械图像,涵盖21类常见器械手术剪(Scissors)止血钳(Hemostat)持针器(Needle Holder)手术刀(Scalpel)镊子(Forceps)拉钩(Retractor)吸引器(Suction)

YOLO 394

RK3588部署YOLOv11与RKNN全流程教程

本文详细介绍了在RK3588开发板上部署YOLOv11目标检测模型的全流程。主要内容包括:PC端CUDA/cuDNN环境配置、Anaconda虚拟环境搭建、RKNN Toolkit2安装;YOLOv11模型从PyTorch到ONNX再到RKNN的格式转换;以及RK3588开发板的系统配置、RKNPU驱动安装和最终推理实现。教程提供了完整的代码示例,帮助开发者快速在边缘设备上实现高效的AI推理应用,涵盖从环境准备到实时检测的全套解决方案。

FJN110的博客 374

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

上一篇: 虾皮二面:为什么数据库连接池不采用 IO 多路复用?
下一篇: 面试官:说说 Cookie、Session、Token、JWT?
肥肥技术宅
博客等级 码龄4年 2408粉丝 3594原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值