浅谈AQS
1.前言
AQS是AbstractQueuedSynchronizer(抽象同步队列)的简写,它是实现同步器的基础组件,并发包下的锁就是通过AQS实现的。作为开发者可能并不会直接用到AQS,但是知道其原理对于架构设计还是很有帮助的。
那为什么说浅谈呢,因为我也仅仅是根据书加上自己的想法来看AQS。
2.LockSupport工具类
在正式讲解AQS之前,我们需要先了解一下LockSupport类,他的主要作用就是用来阻塞和唤醒线程。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是通过Unsafe类实现的。接下来我们介绍一下LockSupport类的主要方法。
park()
前面我们已经说过,LockSupport类与每个使用它的线程都会关联一个许可证,如果调用park方法的线程持有许可证,那么就会立马返回,否则就会被阻塞挂起。
System.out.println("begin park!");
LockSupport.park();
System.out.println("end park!");
上述代码会在输出begin park之后进入阻塞状态。因为默认情况下是不具有许可证的。
只有在其他线程调用unpark(Thread t)方法并且将该线程作为参数,该线程才能返回。如果其他线程调用了该线程的interrupt()方法,设置了中断标志,或者线程被虚假唤醒(parkNanos方法可以指定阻塞一段时间后自己唤醒,所以可能会出现虚假唤醒!),那么该线程也会返回,所以调用park()方法的时候最好使用循环条件判断方式。
不过使用park阻塞的线程被其他线程中断返回时并不会抛出InterruptedException。
park(Object blocker)
public static void park(Object blocker) {// 获取调用线程Thread t = Thread.currentThread();// Thread类中有一个变量parkBlocker,用来存放park方法传递的blocker对象// 设置该线程的blocker变量setBlocker(t, blocker);// 挂起线程UNSAFE.park(false, 0L);// 线程被激活以后清楚blocker变量setBlocker(t, null);
}
当线程在没有持有许可证调用该方法时,会被阻塞挂起,同时blocker对象会被记录到线程内部。
我们可以使用LockSupport.park(this),这样当程序运行以后,我们使用jstack pid打印线程堆栈可以查看到具体是哪个类被阻塞了。
unpark(Thread thread)
当一个线程调用unpark的时候,如果作为参数的thread没有持有许可证,则会让线程持有。
如果thread之前因为调用park阻塞挂起,则调用unpark后会被唤醒。
如果thread之前还没有调用park,则在调用unpark以后,如果调用park则会立即返回。
3.AQS
初识AQS

由类图我们可以知道,AQS是一个FIFO双向队列,通过节点head和tail记录队首队尾元素。
Node
Node节点内部的SHARED用来标记该线程是在获取共享资源时被阻塞挂起放入AQS队列的,EXCLUSIVE用来标识该线程是获取独占资源时被阻塞挂起放入AQS队列的。
在Node节点内部有一个成员变量waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要唤醒)、CONDITION(线程在条件队列里等待)、PROPAGATE(释放资源时需要通知其他节点)。
ConditionObject
ConditionObject和Node一样是AQS的内部类。它用来结合锁实现线程同步,其可以访问AQS的内部变量(state和AQS阻塞队列)。
ConditionObject是条件变量,每个条件变量对应一个条件队列,我们可以看到ConditionObject中有两个指针,分别指向条件队列的队尾和队头。条件队列用来存放调用条件变量的await方法后被阻塞的线程。
state
在AQS中,维护了一个单一变量state,对于不同的实现其有不同的意义:
- 在ReentrantLock中,state表示重入式锁的可重入次数
- 在ReentrantReadWriteLock中,state的高16位用于表示读锁的可获取次数,低16位用于表示写锁的可重入次数。(state的类型是int,占用4个字节)
Shared与Interruptibly
方法名中带有Shared的方法表示获取或释放共享资源,如acquireShared(int arg)
方法名中带有Interruptibly表示对中断做出响应,当线程调用了带Interruptibly的方法,如果这时被其他线程中断,那么就会抛出InterruptedException返回。
AQS工作流程
独占方式下
获取资源
首先使用tryAcquire尝试获取资源,获取成功直接返回,失败则将当前线程封装为EXCLUSIVE的节点插入到AQS阻塞队列尾部并使用LockSupport.park(this)挂起自己。
public final void acquire(int arg) {// tryAcquire的具体实现要由子类来完成,AQS中并不提供实现if (!tryAcquire(arg) &&// addWaiter的作用是将当前线程封装为独占类型的节点插入AQS阻塞队列,并且将该节点返回// acquireQueuedacquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
释放资源
当一个线程调用 release(int arg) 方法时会尝试使用 tryRelease 操作释放资源,这里是设置状态变量 state 的值,然后调用LockSupport.unpark(thread) 方法激活 AQS 队列里面被阻塞的一个线程 (thread)。被激活的线程则使用tryAcquire尝试,如果条件满足则激活继续向下运行,否则被放入AQS继续阻塞。
public final boolean release(int arg) {if (tryRelease(arg)) {// 释放资源成功则尝试激活线程Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}
共享方式下
获取资源
线程调用acquireShared(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源(修改state的值),成功直接返回,失败则将当前线程封装为Node.SHARED的Node节点插入到AQS阻塞队列的尾部。
public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);
}
释放资源
当一个线程调用 releaseShared(int arg) 时会尝试使用 tryReleaseShared 操作释放资 源,这里是设置状态变量 state 的值,然后使用 LockSupport.unpark(thread)激活 AQS 队 列里面被阻塞的一个线程 (thread)。跟独占模式下的流程差不多。
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}
注意
AQS类并没有提供tryAcquire、tryRelease、tryAcquireShare、tryReleaseShare方法,这些方法都需要子类实现。
同时如果你想使用AQS实现一个自己的锁,那你还需要根据情景重写isHeldExclusively方法,用来判断锁是被当前线程共享还是独占。
条件变量
条件变量拥有一个条件队列,条件队列要跟AQS阻塞队列区别开。
当多个线程同时调用lock.lock()方法获取锁时,只有一个线程获取到了锁,其 他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面,并做自旋 CAS 尝试获取锁。
如果获取到锁的线程又调用了对应的条件变量的 await() 方法,则该线程会释放获取 到的锁,并被转换为 Node 节点插入到条件变量对应的条件队列里面。

相关文章:
浅谈AQS
1.前言 AQS是AbstractQueuedSynchronizer(抽象同步队列)的简写,它是实现同步器的基础组件,并发包下的锁就是通过AQS实现的。作为开发者可能并不会直接用到AQS,但是知道其原理对于架构设计还是很有帮助的。 那为什么说…...
关于服务连接器(Servlet)你了解多少?
Servlet 1 简介 Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义…...
面对学员的投诉,中创教育是如何处理的?
客户满意度的检测指标是客户的期望值和服务感知之间的差距。当顾客购买商品时,对商品本身和企业的服务都抱有良好的愿望和期盼值,如果这些愿望和要求得不到满足,就会失去心理平衡,由此产生的抱怨和想"讨个说法"的行为&a…...
算法问题——排序算法问题
摘要 查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中。因为其实现代码较短,应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗,只要熟悉了思想,灵活运用也不是难事。一般在面试中最常…...
ArcGIS网络分析之构建网络分析数据集(一)
说明: 1. 本文主要用于演示网络分析服务的搭建过程。所以在此不会深入讨论网络分析服务的每一个细节,本文的目的就是让初学者学会使用网络分析服务进行基本的分析(主要针对后续的WEB开发):路径分析,最近设施点分析,以及服务区分析。 2.关于OD成本矩阵分析,多路径配送,…...
微电影的行业痛点有哪些?
微电影全称微型电影,又称微影。是指能够通过互联网新媒体平台传播(几分钟到60分钟不等)的影片,适合在移动状态、短时休闲状态下观看,具有完整故事情节的“微(超短)时”(几分钟-60分钟)放映、“微(超短)周期制作(7-15天…...
spark3.0源码分析-driver-executor心跳机制
前言 driver和executor心跳机制分为两种机制: 1、executor发送心跳机制 2、driver接受心跳机制 至于为何要分为两种,原因是在分布式场景中,服务的稳定性是无法保障的,例如executor宕机后无法发送心跳,故driver端需要…...
数据分析就要选择这款免费报表工具
对于一家企业来说,在日常运营的过程中本身就会产出很多的数据,那么这些数据本身就应该形成报表。可是如果只是选择手工的一种操作,确实需要浪费大量的人力物力。伴随着科技进入到快速发展的阶段,市面上更是出现了很多报表工具可以…...
node学习-3:服务器渲染和客户端渲染
1. 概念 一.服务端渲染,后端嵌套模板,后端渲染模板,SSR(后端把页面组装好) 做好静态页面,动态效果 把前端代码提供给后端,后端则把静态html以及里面的假数据给删除掉 通过模板进行动态生成h…...
LeetCode刷题笔记和周赛题解总目录
之前一段时间一直在刷LeetCode,在上面积累了很多笔记,这些笔记是做题过程中的一些重要积累和心得,现在将它们汇总和总结至此,此博客将不断更新。 刷题笔记(提供md和pdf两种格式可供下载,不断更新) LeetCode刷题笔记 …...
用类比方式学习编程中函数递归(个人理解仅供参考)(内含汉诺塔问题的求解)
目录 1.前言 2.递归的数学模型 3.相关的c语法 4.将递归的数学模型写成编程语言 5.利用类比方法将实际问题的代码写成函数递归的形式 例1: 例2: 6.汉诺塔问题的求解 1.前言 本人在学习函数递归编程方法的过程中,发现用类比的方式学习递归法可帮助我们在各种编…...
【云原生之Docker实战】使用Docker部署Taskover开源个人任务管理工具
【云原生之Docker实战】使用Docker部署Taskover 开源个人任务管理工具 一、Taskover介绍1.Taskover 简介2.Taskover功能二、检查本地docker环境1.检查系统版本2.检查docker版本3.检查docker状态4.检查docker compose版本三、下载Taskover镜像四、部署Taskover应用1.创建安装目录…...
5、SQL编程开发与注意事项
1.1 导入数据 导入测试库: 文档地址: https://dev.mysql.com/doc/employee/en/sakila-structure.html下载地址: https://github.com/datacharmer/test_db导入测试库: mysql -uroot -p -S < employees.sql 1.2 库操作 增:create database test character set utf8;删:d…...
Allegro如何通过视图显示区分动态和静态铜皮操作指导
Allegro如何通过视图显示区分动态和静态铜皮操作指导 用Allegro做PCB设计的时候,通常动态和静态铜皮是无法通过视图显示区分的,只能通过show element查看得知,如下图 左边铜皮是动态铜皮,右边是静态铜皮 但Allegro可以通过一些设置让动静态铜皮以不同效果显示出来 具体操…...
测试开发之Django实战示例 第十一章 渲染和缓存课程内容
第十一章 渲染和缓存课程内容在上一章中,使用了模型继承和通用关系建立弹性的课程、章节和内容的关联数据模型,并且建立了一个CMS系统,在其中使用了CBV,表单集和AJAX管理课程内容。在这一章将要做的事情是:创建公开对外…...
90%企业在探索的敏捷开发怎么做?极狐GitLab总结了这些逻辑与流程
本文来自: 彭亮 极狐(GitLab) 高级产品经理 毛超 极狐(GitLab) 研发工程师 极狐(GitLab) 市场部内容团队 “敏捷” 是指能够驾驭变化,保持组织竞争优势的一种能力。自 2001 年《敏捷宣言》以来,敏捷及敏捷开发理念逐渐席卷全球。中国信通院《…...
LeetCode-257. 二叉树的所有路径
目录题目分析递归法题目来源 257. 二叉树的所有路径 题目分析 前序遍历以及回溯的过程如图: 递归法 1.递归函数参数以及返回值 要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代…...
测试用例该怎么设计?—— 日常加更篇(下)
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…...
Java基础:接口
1.接口的概念 当不是所有子类, 而是多个子类都包含一个方法时, 为了代码书写规范性, 可以用自定义的接口来统一该方法的书写规范. 所以接口可以看作是一种书写规则. 接口是对行为的抽象 抽象类一般是书写在父类当中, 接口是单独书写的, 不是一种类 2.接口的定义和使用 3.接口…...
vuex基础入门:uniapp实现用户登录授权实战
1.背景 vuex是数据共享方案之一,本文以微信小程序登录授权为例介绍一下vuex常用属性state、getters、mutations、actions. 2.基于uniapp实现微信小程序登录授权流程 1.凡是需要用户登录授权信息的页面创建时created方法中需要判断用户是否登录,需要使用本地缓存的token调用服务…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
