2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放
首先我们从java.util.concurrent.locks包中的AbstraceQueuedSynchronizer说起,在下文中称为AQS。
AQS是一个用于构建锁和同步器的框架。例如在并发包中的ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等都是基于AOS构建,这些锁都有一个特点,都不是直接扩展自AQS,而是都有一个内部类继承自AQS。为什么会这么设计而不是直接继承呢?简而言之,锁面向的是使用者,同步器面向的是线程控制,在锁的实现中聚合同步器而不是直接继承AQS很好的隔离了二者所关注的领域。
AbstractQueuedSynchronizer在内部依赖一个双向同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将该线程和等待状态信息构造成一个节点并将其加入到同步队列中。Node节点以AQS的内部类存在,其字段属性如下:
|
AbstractQueuedSynchronizer$Node |
|
|
属性 |
描述 |
|
volatile int waitStatus |
等待状态,并不是同步状态,而是在队列中的线程节点等待状态(Node节点中一共定义四种状态) CANCELLED = 1 //线程由于超时或被中断会被取消在队列中的等待,被取消了的线程不会再被阻塞,即状态不会再改变 SIGNAL = -1 //后继节点处于等待状态,当前节点释放锁或者取消等待时会通知后继节点 CONDITION = -2 //暂时忽略,涉及Condition PROPAGATE = -3 //下一次共享式同步状态获取将会无条件传播下去 |
|
volatile Node prev |
前驱节点 |
|
volatile Node next |
后继节点 |
|
volatile Thread thread |
保持对当前获取同步状态线程的引用 |
|
Node nextWaiter |
等待队列中的后继节点,同时也表示该节点是共享模式(SHARED)还是独占(EXCLUSIVE)模式。它们共用一个字段。因为在等待队列中的线程一定是独占模式。所以如果nextWatiter == SHARED,那么表示该节点为共享模式。 |
在AQS同步器中由一个头节点和尾节点来维护这个同步队列。
|
AbstractQueuedAbatract |
|
|
属性 |
描述 |
|
private transient volatile Node head |
同步队列头节点 |
|
private transient volatile Node tail |
同步队列尾节点 |

以上内容我们需要知道一点的就是:同步器中是依靠一个同步队列来完成的同步状态管理,当线程获取锁(或者称为同步状态)失败时,会将线程构造为一个Node节点新增到同步队列的尾部。
在锁的获取当中,并不一定是只有一个线程才能持有这个锁(或者称为同步状态),所以此时有了独占模式和共享模式的区别,也就是在Node节点中由nextWait来标识。比如ReentrantLock就是一个独占锁,只能有一个线程获得锁,而WriteAndReadLock的读锁则能由多个线程同时获取,但它的写锁则只能由一个线程持有。本章先介绍独占模式下锁(或者称为同步状态)的获取与释放,在此之前要稍微提一下“模板方法模式”,在AQS同步器中提供了不少的模板方法,关于模板方法模式可以移至《模板方法模式》,总结就是一句话:定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。
1)独占模式同步状态的获取
|
AbstractQueuedSynchronizer |
|
|
方法 |
描述 |
|
public final void acquire(int arg) |
独占模式下获取同步状态,忽略中断,即表示无论如何也会在获得同步状态后才返回。 |
此方法即为一个模板方法,它的实现代码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire:AQS中有一个默认实现,其默认实现即抛出一个UnsupportedOperationException异常,意为默认下独占模式是不支持此操作的。而这个操作在子类又是怎样的呢?我们可以通过查看ReentrantLock中的Sync实现:
//ReentrantLock
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
可以看到在AQS的其中一个实现中,ReentrantLock$Sync对它进行了重写,具体意义在这里不做讨论。这个在AQS定义的方法表示该方法保证线程安全的获取同步状态,如果同步状态获取失败(返回false)则构造同步节点并将节点加入到同步队列的尾部,这个操作即是addWaiter方法的实现:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //将线程构造成Node节点。
/*尝试强行直接挂到同步队列的尾部*/
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
/*如果此时有多个线程都在想把自己挂到同步队列的尾部,上面的操作就会
失败,此时将“无限期”线程安全的等待着挂到同步队列的尾部*/
enq(node);
return node;
}
在enq的实现中实际就是一个for“死循环”,其目的就是直到成功地添加到同步队列尾部才推出循环。
在获取同步状态失败(tryAcqurie) ->构造节点(addWaiter)->添加到同步队列尾部(addWaiter)过后,接下来就是一个很重要的操作acquireQueued自旋。这个动作很重要,其目的就在于每个节点都各自的在做判断是否能获取到同步状态,每个节点都在自省地观察,当条件满足获取到了同步状态则可以从自旋过程中退出,否则继续。
final boolean acquireQueued(final Node node, int qrg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从上面的代码实现我们可以看到,尽管每个节点都在“无限期”的获取锁,但并不是每个节点能有获取锁的这个资格,而是当它的前驱节点是头节点时才会去获取锁(tryAcquire)。当这个节点获取同步状态时,接下来的方法shouldParkAfterFailedAcquire则会判断当前线程是否需要被阻塞,而这个判断方法则是通过它的前驱节点的waitStatus判断。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //首先获取当前节点的前驱节点等待状态
if (ws == Node.SIGNAL) //当前线程需要被阻塞,即需要被unpark(唤醒)
return true;
if (ws > 0) { //pred.waitStatus == CANCELLED
do {
node.prev = pred = pred.prev; //前驱节点等待状态已经处于取消,即不会再获取同步状态时,把前驱节点从同步状态中移除。
} while (pred.waitStatus > 0);
pred.next = node;
} else { //pred.waitStatus == CONDITION || PROPAGATE
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
如果调用该方法判断为当前线程需要被阻塞(返回true),则接着执行parkAndCheckInterrupt阻塞当前线程,直到当前线程被唤醒的时候才从parkAndCheckInterrupt返回。
关于独占模式获取同步状态可以总结为下面一段话:
AQS的模板方法acquire通过调用子类自定义实现的tryAcquire获取同步状态失败后->将线程构造成Node节点(addWaiter)->将Node节点添加到同步队列对尾(addWaiter)->每个节点以自旋的方法获取同步状态(acquirQueued)。在节点自旋获取同步状态时,只有其前驱节点是头节点的时候才会尝试获取同步状态,如果该节点的前驱不是头节点或者该节点的前驱节点是头节点单获取同步状态失败,则判断当前线程需要阻塞,如果需要阻塞则需要被唤醒过后才返回。
2).独占模式同步状态的释放
|
AbstractQueuedSynchronizer |
|
|
方法 |
描述 |
|
public final boolean release(int arg) |
释放同步状态,并唤醒后继节点 |
当线程获取到了同步状态并且执行了相应的逻辑过后,此时就应该释放同步状态。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //唤醒头节点的后继节点
return true;
}
return false;
}
AQS中的release释放同步状态和acquire获取同步状态一样,都是模板方法,tryRelease释放的具体操作都有子类去实现,父类AQS只提供一个算法骨架。
private void unparkSucessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) //ws != CANCELLED
compareAndSetWaitStatus(node, ws, 0); //利用CAS将当前线程的等待状态置为CANCELLE
Node s = node.next;
if (s == null || s.waitSatatus > 0) { //如果当前线程的后继节点为空,则从同步队列的尾节点开始向前寻找当前线程的下一个不为空的节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = r;
}
if (s != null)
LockSuport.unpark(s.thread); //如果当前线程的后继节点不为空,则调用LockSuport.unpark唤醒其后继节点,使得后继节点得以重新尝试获取同步状态
}
对AQS的源码解读才刚刚开始,本节只介绍了AQS在内部使用一个同步队列来管理同步状态,并且介绍了在AQS在模板方法模式的基础上实现独占模式同步状态的获取与释放。下一节会继续解读AQS共享模式下同步状态的获取与释放。
2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放的更多相关文章
- 3.从AbstractQueuedSynchronizer(AQS)说起(2)——共享模式的锁获取与释放
在上节中解析了AbstractQueuedSynchronizer(AQS)中独占模式对同步状态获取和释放的实现过程.本节将会对共享模式的同步状态获取和释放过程做一个解析.上一节提到了独占模式和共享模 ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- AQS源码深入分析之独占模式-ReentrantLock锁特性详解
本文基于JDK-8u261源码分析 相信大部分人知道AQS是因为ReentrantLock,ReentrantLock的底层是使用AQS来实现的.还有一部分人知道共享锁(Semaphore/Count ...
- 4.从AbstractQueuedSynchronizer(AQS)说起(3)——AQS结语
前两节的内容<2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放> .<3.从AbstractQueuedSynchronize ...
- AbstractQueuedSynchronizer AQS框架源码剖析
一.引子 Java.util.concurrent包都是Doug Lea写的,来混个眼熟 是的,就是他,提出了JSR166(Java Specification RequestsJava 规范提案), ...
- 8.初识Lock与AbstractQueuedSynchronizer(AQS)
1. concurrent包的结构层次 在针对并发编程中,Doug Lea大师为我们提供了大量实用,高性能的工具类,针对这些代码进行研究会让我们对并发编程的掌握更加透彻也会大大提升我们队并发编程技术的 ...
- 初识Lock与AbstractQueuedSynchronizer(AQS)
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放
上期的<全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础>中介绍了什么是AQS,以及AQS的基本结构.有了这些概念做铺垫之后,我们就可以正 ...
- 013-并发编程-java.util.concurrent.locks之-AbstractQueuedSynchronizer-用于构建锁和同步容器的框架、独占锁与共享锁的获取与释放
一.概述 AbstractQueuedSynchronizer (简称AQS),位于java.util.concurrent.locks.AbstractQueuedSynchronizer包下, A ...
随机推荐
- Apache Storm 1.1.0 发布概览
写在前面的话 本人长期关注数据挖掘与机器学习相关前沿研究.欢迎和我交流,私人微信:846731084 我自己测试了一下这个版本,总的来说更加稳定,新增的特性并没有一一测试,仅凭kafk-client来 ...
- CF #404 (Div. 2) B. Anton and Classes (贪心)
题意:有一个小朋友,即喜欢下象棋,还喜欢编程,于是他打算上这两种课的兴趣班,这两种课有着不同的上课时间,他想让两堂课之间的休息时间最多,问最大时间是多少 思路:看到这道题的第一反应就是贪心,于是用结构 ...
- Centos7上安装Kubernetes集群部署docker
一.安装前准备1.操作系统详情需要三台主机,都最小化安装 centos7.3,并update到最新 [root@master ~]# (Core) 角色 主机名 IPMaster master 192 ...
- day001-html知识点总结(二)不常见但很重要的元素汇总
一..vertical-align:设置垂直对齐方式,主要用于: 1.单元格内容的垂直对齐 2.对于行内块级元素,如<img>,设置行内元素的基线相对于该行内块级元素的所在行的基线对齐,例 ...
- C# 超高速高性能写日志 代码开源
1.需求 需求很简单,就是在C#开发中高速写日志.比如在高并发,高流量的地方需要写日志.我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的. ...
- Android批量验证渠道、版本号
功能:可校验单个或目录下所有apk文件的渠道号.版本号使用说明:1.copy需要校验的apk文件到VerifyChannelVersion目录下2.双击运行VerifyChannelVersion.b ...
- let 和 const 关键字
看了阮老师的ES6入门再加上自己的一些理解整理出的学习笔记 let关键字 跟var相比,不会提升为全局变量,始终是块级作用域{} 注意点: 1: 不能在同一个块级作用域内声明同名变量 2: (如果当前 ...
- Twitter数据抓取的方法(二)
Scraping Tweets Directly from Twitters Search Page – Part 2 Published January 11, 2015 In the previo ...
- Nginx配置同一个域名同时支持http与https两种方式访问
Nginx配置同一个域名http与https两种方式都可访问,证书是阿里云上免费申请的 server{listen 80;listen 443 ssl;ssl on;server_name 域名;in ...
- Azure WAF防火墙工作原理分析和配置向导
Azure WAF工作原理分析和配置向导 本文博客地址为:http://www.cnblogs.com/taosha/p/6716434.html ,转载请保留出处,多谢! 本地数据中心往云端迁移的的 ...