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 ...
随机推荐
- Lvs工作原理
DR模式的工作过程: 当一个client发送一个WEB请求到VIP,LVS服务器根据VIP选择对应的real-server的Pool,根据算法,在Pool中选择一台Real-server,LVS在ha ...
- 管中窥豹——从对象的生命周期梳理JVM内存结构、GC调优、类加载、AOP编程及性能监控
如题,本文的宗旨既是透过对象的生命周期,来梳理JVM内存结构及GC相关知识,并辅以AOP及双亲委派机制原理,学习不仅仅是海绵式的吸收学习,还需要自己去分析why,加深对技术的理解和认知,祝大家早日走上 ...
- net.sz.framework 框架 轻松搭建数据服务中心----读写分离数据一致性,滑动缓存
前言 前文讲述了net.sz.framework 框架的基础实现功能,本文主讲 net.sz.framework.db 和 net.sz.framework.szthread; net.sz.fram ...
- SVG格式转Visio的vsd格式方法,附带C#动态调用Office的Com组件方法
SVG格式可以直接显示在网页上面,用来实现诸如统计Chart图表.流程图.组织结构图的功能.为了使图像可以下载下来以便于修改,可以将SVG转为Visio的vsd格式.方法很简单,主要是使用Visio组 ...
- form表单的enctype
form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype="application/x- www-form-urlencoded".这 ...
- 严格模式下的javascript
arguments: ECMA5移除了实参对象(arguments),在非严格模式下函数里的arguments只是一个标识符,在严格模式下它变成了保留字.严格模式下无法使用arguments作为形参名 ...
- Weex的环境搭建以及集成到Android项目
最近由于公司的需要,初步研究了Weex,Weex是阿里开发的一个web的框架,官方的介绍如下: Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能.可扩展的 native 应 ...
- 论.net平台的切身感触(惑)
这篇博客只是作者客观看法,不喜勿喷,条条大路通罗马,路不同风景也不一样,接下来的路该怎么走? 简介:作者.net程序员一枚,工作已有四年,接触过.net平台winform,webform,mvc的开发 ...
- Reids详解-抄本
1.redis是什么 Redis 是一个基于内存的高性能key-value数据库.是一个开源的.使用C语言编写的.支持网络交互的.可基于内存也可持久化的Key-Value数据库. 2.redis的特点 ...
- php 启动过程 - reqeust RSHUTDOWN 过程
php 启动过程 - reqeust RSHUTDOWN 过程 概述 request RSHUTDOWN 过程在请求结束后调用 调用触发 同 request RINIT 过程一样, 先是用 apach ...