java conditionobject_Java AbstractQueuedSynchronizer源码阅读4-ConditionObject
AbstractQueuedSynchronizer为锁机制维护了一个队列,需要获取锁的线程们排在队列中,只有排在队首的线程才有资格获取锁。
ConditionObject是AbstractQueuedSynchronizer的内部类,它为锁机制维护了另一个队列,如果线程排在了该队列中,说明这个线程需要在某种条件满足后,才被唤醒。
前一个队列是用于锁的争用的,称之为syn queue。后一个队列是用于条件等待的,称之为condition queue。这两个队列之间是这样协作的:当线程拿到锁后,发现条件未满足,便释放锁并挂到condition queue中去;当条件满足后,线程会被唤醒,并挂到syn queue中去重新获取锁。具体的应用场景可见Java--Lock&Condition的理解中提到的生产者和消费者模型。
本文主要是对ConditionObject的实现做简单的介绍。
Condition queue
ConditionObject主要是维护了一个condition queue,代码如下所示
public class ConditionObject implements Condition, java.io.Serializable {
//First node of condition queue.
private transient Node firstWaiter;
//Last node of condition queue.
private transient Node lastWaiter;
......
}
condition queue也是一个Node队列,这和syn queue同样,不过syn queue是通过Node的prev和next指针形成的双向队列,而condition queue则是通过Node的nextWaiter形成的单向队列。ConditionObject仅是记录了condition queue的队首和队尾。
下面结合代码简述一下ConditionObject中几个方法。
awaitUninterruptibly()
该方法就是当前线程要在某个条件上等待,要加入condition queue了。
步骤:
挂入condition queue;
释放锁;
挂起线程;
线程唤醒后重新尝试获取锁。
代码及注释如下。
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();//将当前线程挂入condition queue
int savedState = fullyRelease(node);//释放锁
boolean interrupted = false;
while (!isOnSyncQueue(node)) {//线程是不是在syn queue里
LockSupport.park(this);//挂起当前线程
if (Thread.interrupted())
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted) //被唤醒后则重新开始尝试获取锁
selfInterrupt();
}
这里面有个while,用来判断线程是不是在syn queue里。针对这个循环可做两点说明:
node由addConditionWaiter()返回,是一个waitStatus=Node.CONDITION的node,所以,第一次执行判断时,必入循环,当前线程被挂起;
线程被唤醒,从挂起处继续执行,此时,会继续执行while内的判断。直到确认当前线程已经在syn queue队列上,才会尝试获取锁。那么,node又是被谁放到syn queue中的呢?是和await()方法对应的signal()方法。
其中
addConditionWaiter()是ConditionObject的私有方法
fullyRelease()和isOnSyncQueue()是AbstractQueuedSynchronizer为Conditions实现的方法。
addConditionWaiter()
将当前线程挂入condition queue,代码及注释如下。
private Node addConditionWaiter() {
Node t = lastWaiter;
//如果队尾已经被cancel了,就清理一次condition queue,将所有的cancelled node出队
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//新建node,关联到当前线程,并加入condition queue
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
该方法分为两步。因为Node是从队尾加入condition queue的,所以第一步是判断condition queue的队尾是否已经被cancel了,如果是,就调用unlinkCancelledWaiters()从队头开始将所有的cancelled node都出队。清理完cancelled node后,队尾就是有效的node了,此时,新建一个关联到当前线程的node,将该node添加到队列中,并设置为新的队尾。
unlinkCancelledWaiters()
清除队列中所有的cancelled node。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;//当前节点(就好比for循环中的i)
Node trail = null;//记录当前节点前面最近的一个有效节点(未被取消的节点)
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {//如果当前节点被取消了,就将当前节点出队
t.nextWaiter = null;
if (trail == null)//如果tiral为空,说明当前节点前面没有有效节点,而当前节点又被取消了
//说明从当前节点往前的所有节点都被取消了,队首自然要往后更新
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;//没有取消,则更新所谓“最近的有效节点”
t = next;//当前节点更新为下一个(就好比for循环中的++i)
}
}
signal()
唤醒condition queue的队首,主要的代码其实就是对doSignal()的调用。
doSignal()
唤醒队首,代码及注释如下。
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)//队首的nextWaiter是不是指向空(也即队列里是不是只有一个node,即队首)
//这一步同时更新了队首,相当于将原先的队首出队了
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&//将队首迁移到syn queue
(first = firstWaiter) != null);//如果迁移失败了,说明原先的队首被取消了,尝试处理更新后的队首
} //如果更新后的队首为空,说明队列已经被清空了,就无需再处理了
doSignal()在源码中有这么一句注释"Split out from signal in part to encourage compilers to inline the case of no waiters".这句话的含义如下:
这里单独实现doSignal()接口的意义在于,使得signal()的代码看起来十分简单,不会直接包括循环体,编译器在编译的时候,将更倾向于将signal()当做inline function。这样,在没有任何waiters(即condition queue为空,也即firstWaiter == null)的情况下, signal()作为inline function,性能将得到更明显的提升。
transferForSignal()
将node从condition queue迁移到syn queue。
代码及注释如下。
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//如果node被取消了,就无需再进行什么迁移操作了
return false;//迁移失败,返回后doSignal()会去处理下一个node
Node p = enq(node);//将node加入syn queue
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//尝试唤醒node关联的线程
//没唤醒也没关系,已经加入syn queue了,总会被syn queue前面的node唤醒的
LockSupport.unpark(node.thread);
return true;
}
ConditionObject的方法介绍就到这里了,下面是对CAS和inline function的一些解释。
对condition queue的操作未用任何CAS操作?
比如说addConditionWaiter()和singal()方法在修改队首和队尾,或是在修改nextWaiter指针时,都未使用任何CAS操作。这是因为,一个线程如果正在调用ConditionObject的方法的话,说明它一定获得了ConditionObject所隶属的锁。此时,能够保证一次性只有一个线程正在修改该锁对应的condition queue。
在上文解释的代码中,只有transferForSignal()使用到了CAS方法。因为该方法是想要改变其他线程的状态,而其他线程的状态还可能因为其他原因改变,所以其中使用了CAS方法。
inline function
inline function提升性能之处在于,编译器在编译的时候,会将代码整个替换到函数调用所在位置,省去了函数调用的耗时。
函数调用的耗时我倒是知道些,调用时需要保存现场信息,开辟新的堆栈,返回时还要恢复现场信息。
但是,为何只有简短的函数适合内联呢?这是因为内联增大了代码的体积。代码在执行的时候是要被加载到内存的。若函数A采取调用的方式,不论被引用了多少次,代码本身就只占一份A的内存空间。若A采取内联的方式,若被引用了两次,代码本身就要占两份的内存空间。一旦A被更多的地方引用,代码占用的内存就会显著增大,从而影响到运行时的性能。
这里有篇针对inline function的问答,感觉挺好:
http://www.learncpp.com/cpp-tutorial/75-inline-functions/
为防止链接失效,特截图一张吧。
inline function
java conditionobject_Java AbstractQueuedSynchronizer源码阅读4-ConditionObject相关推荐
- Java struts 2 源码阅读入门
一 搭建源码阅读环境 首先新建一个struts 2 实例工程,并附着源码: 在Eclipse中新建一个动态web工程:完成后结构如下: 添加如下图的包:可以直接拖到lib文件夹:完成后如下: 新建一个 ...
- Java String类源码阅读笔记
文章目录 一.前置 二.String类源码解析 1.String类继承关系 2.成员变量 3.构造方法 4.长度/判空 5.取字符 6.比较 7.包含 8.hashCode 9.查询索引 10.获取子 ...
- java中talent-aio_talent-aio源码阅读小记(一)
近来在oschina上看到一个很火的java 即时通讯项目talent-aio,恰巧想了解一下这方面的东西,就阅读了一下项目的源码,这里对自己阅读源码后的一些心得体会做一下备忘,也希望能够对其他项目中 ...
- java工具类源码阅读,java学习日记第二天(实用的工具类和源码解析一Arrays)
本帖最后由 三木猿 于 2020-9-18 11:17 编辑 每日名言 学者须先立志.今日所以悠悠者,只是把学问不曾做一件事看,遇事则且胡乱恁地打过了,此只是志不立. --朱熹 工作中经常会用到一些工 ...
- java java.lang.enum_源码阅读-java基础-java.lang.Enum
1.引言 枚举类型是 JDK 5 之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量.相比与常量(public static final定义),在安全性.指意性.可读性方面更胜一筹.另外它可 ...
- java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO
来源:木杉的博客 , imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/ Java传统IO是不支持中断的, ...
- Android Framework源码阅读计划(2)——LocationManagerService.java
Android Framework源码阅读计划 Android Framework源码阅读计划(1)--LocationManager.java Android Framework源码阅读计划(2)- ...
- 面试官系统精讲Java源码及大厂真题 - 30 AbstractQueuedSynchronizer 源码解析(上)
30 AbstractQueuedSynchronizer 源码解析(上) 不想当将军的士兵,不是好士兵. 引导语 AbstractQueuedSynchronizer 中文翻译叫做同步器,简称 AQ ...
- 【java】java JUC 同步器框架 AQS AbstractQueuedSynchronizer源码图文分析
1.概述 转载:JUC锁: 锁核心类AQS详解 AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore ...
最新文章
- poj 2362 Square
- rs485调试软件_5种RS485切换方向的方法及优劣势分析
- 配置深度学习环境的重要资料
- 数据库连接池为什么要用threadlocal呢?(不用会怎样?)
- Map字符串类型去掉空格处理
- mysql中基本的DDL语句(关注一下,以后会继续更新喔!)
- python xlutils函数,python3:xlrd、xlwt、xlutils处理excel文件
- Educational Codeforces Round 30 A[水题/数组排序]
- 会计云课堂实名认证后怎么更改_会计云课堂网上听课步骤详解
- 揭开CSS的绝对定位真实的面纱(二)
- 串灯控制盒去掉怎么接_彩灯控制器坏了怎么办
- 2021-09-10 网安实验-文件修复-BMP图片隐写
- 请教苹果虚拟机自动配置序列号ID脚本
- “金三银四“,敢不敢“试”?
- 如何删除2345SafeCenterSvc
- 计算机组装维护教学工作总结,计算机组装与维护教师工作总结_2
- 如何修改美食大战老鼠服务器,《美食大战老鼠》联运区组停止运营公告
- CINTA:同构,同态与商群
- 05【React再造之旅】从零实现一个React(下)
- 【C#】int与int?