AQS初体验
AQS初体验
AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。所谓框架,AQS使用了模板方法的设计模式,为我们屏蔽了诸如内部队列等一系列复杂的操作,让我们专注于对锁相关功能的实现。
获取锁
既然涉及到锁竞争的问题,必然需要一个标志位来表示锁的状态,AQS中提供了state这样一个成员变量,为了安全的操作state,我们需要使用原子操作。将state从0修改为1就代表这个线程已经持有了这把锁。
但竞争锁的线程绝对不会只是一个,其他未竞争到锁的线程该如何进行处理?
第一个答案可能是重试,重试虽好,但是可不能贪杯,如果竞争很严重,无数的线程在不断的重新尝试获取锁,我们的CPU早晚会吃不消。
第二个比较好的方式就是排队,持有锁的线程释放锁之后,通知下一个线程去获取锁,避免了不必要的CPU损失。但是值得注意的是,即使是从队列中被唤醒的线程去获取锁也依旧可能获取不到的,因为无时无刻都有新加入的线程来竞争锁。
AQS实际上就是使用了双端队列来解决了这个问题的。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()如果失败将执行acquireQueued()中的addWaiter()方法,即尝试加入等待队列。这个等待队列使用了双端队列进行实现,在AQS中定义了一个Node的数据结构,AQS中维护着head和tail两个成员变量。
在单线程中插入队列尾部很简单,只需要将原来的tail的next指向新插入的节点,并且将tail重新设置为新插入的节点。但是在多线程环境中,很有可能发生多个线程同时插入尾部的现象,而上述的插入过程不具有原子性,同时插入的过程必将出现多个操作顺序的混乱,最终导致等待队列的tail节点
AQS在插入tail节点时使用原子操作来保证了插入的可靠。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
插入成功的直接返回了node,而没有插入成功的则执行了enq()函数,在enq()中使用了CAS进行插入。
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;
}
}
}
}
经历这个CAS插入,最后全部的节点都将被插入到队列尾部。
现在,没有获取到锁的线程已经被放进队列了,但是放入队列也代表着我们可以忘了初心。我们的目标是获取锁,而不是进入队列。acquireQueued()就在尝试为我们获取锁。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
简单说就是,检查自己是不是head节点的下一个节点,如果是的话,尝试获取获取锁;如果不是的话,将使用LockSupport的park方法阻塞当前线程,避免造成CPU的浪费。
释放锁
释放锁的过程可以分成两大部分:
- 恢复AQS的状态为无锁状态
- 唤醒等待队列中下一个等待的节点
在第一个过程中,没有排在队头的节点都已经被阻塞了,而唤醒的时机就是前一个节点已经释放锁,所以可以说这个等待队列,实际上是一个唤醒链。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 使用unpark唤醒下一个线程
unparkSuccessor(h);
return true;
}
return false;
}
总结
AQS为我们提供了:
- status 状态同步标示
- Node双端队列 存储竞争锁线程
- 基于Node双端队列的线程唤醒机制
我觉得AQS精华在于,将原来N个线程并发竞争锁降低为1+M(新加入)个。在我们自己实现类似的资源竞争算法中,也可以通过加入队列来降低竞争的并发度,降低CPU的负载压力。
AQS初体验的更多相关文章
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
- Spring之初体验
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...
- Xamarin.iOS开发初体验
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0
- 【腾讯Bugly干货分享】基于 Webpack & Vue & Vue-Router 的 SPA 初体验
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d13a57132ff21c38110186 导语 最近这几年的前端圈子,由于 ...
- 【Knockout.js 学习体验之旅】(1)ko初体验
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
- 在同一个硬盘上安装多个 Linux 发行版及 Fedora 21 、Fedora 22 初体验
在同一个硬盘上安装多个 Linux 发行版 以前对多个 Linux 发行版的折腾主要是在虚拟机上完成.我的桌面电脑性能比较强大,玩玩虚拟机没啥问题,但是笔记本电脑就不行了.要在我的笔记本电脑上折腾多个 ...
- 百度EChart3初体验
由于项目需要在首页搞一个订单数量的走势图,经过多方查找,体验,感觉ECharts不错,封装的很细,我们只需要看自己需要那种类型的图表,搞定好自己的json数据就OK.至于说如何体现出来,官网的教程很详 ...
- Python导出Excel为Lua/Json/Xml实例教程(二):xlrd初体验
Python导出Excel为Lua/Json/Xml实例教程(二):xlrd初体验 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出E ...
随机推荐
- delphi的Socket(有两种分别继承TObject和TComponent的方式)
在Delphi中,对于Windows中的Socket进行了有效的封装.在Delphi中,按其继承关系,可以分层两类:一.TComponent--TAbstractSocket--TCustomSock ...
- qt 心跳设计
网络通信中的心跳设计是为了判断客户端和服务器通信是socket是否处于连接状态,服务端每隔一个固定的时间间隔给客户端放消息,客户端设计一个心跳类,类中有一个定时器,当socket接收到信息时,心跳类记 ...
- Arcgis Server 10.4.1 搭建集群环境
1.准备工作 Arcgis Server 10.4.1 以及许可一枚 共享存储(通过UNC路径访问,如"\\server1\arcgisserver\") 服务器两台(虚拟机也可 ...
- Nodejs操作MySQL - 增删改查
先安装npm模块项目 npm init 安装mysql npm install mysql --save Nodejs 连接msyql // 导入mysql const mysql = require ...
- spark开发常见问题之一:java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
最近在学习研究pyspark机器学习算法,执行代码出现以下异常: 19/06/29 10:08:26 ERROR Shell: Failed to locate the winutils binary ...
- .NET开发框架(一)-框架介绍与视频演示
本文主要介绍一套基于.NET CORE的SPA高并发.高可用的开发框架. 我们暂且称它为:(让你懂.NET)开发框架. 以此为主线,陆续编写教程,讲述如何构建高并发.高可用的框架. (欢迎转载与分享) ...
- 浅入深出Vue:事件处理
上一篇的最后留下了一个 v-on的思考,也就是本章的主题:事件处理 为什么需要事件处理 在前端开发中,经常要面对各种表单.按钮.而这里面就住着一个事件:点击 (click). 前端童鞋们肯定不陌生它, ...
- 【转载】java8中的Calendar日期对象(LocalDateTime)
Java 8 推出了全新的日期时间API,Java 8 下的 java.time包下的所有类都是不可变类型而且线程安全. 下面是新版API中java.time包里的一些关键类: Instant:瞬时实 ...
- MapReduce in MongoDB
MongoDB の MapReduce 在Hadoop的学习中已经接触过MapReduce了,它是一个很成熟的计算模型,将大批量的工作(也就是数据)分解(MAP映射)执行,最后将结果合并成最终的Red ...
- 联盟链FISCO BCOS v2.0.0-rc3 发布
FISCO BCOS是完全开源的联盟区块链底层技术平台,由金融区块链合作联盟(深圳)(简称金链盟)成立开源工作组通力打造.开源工作组成员包括博彦科技.华为.深证通.神州数码.四方精创.腾讯.微众银行. ...