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相关推荐

  1. Java struts 2 源码阅读入门

    一 搭建源码阅读环境 首先新建一个struts 2 实例工程,并附着源码: 在Eclipse中新建一个动态web工程:完成后结构如下: 添加如下图的包:可以直接拖到lib文件夹:完成后如下: 新建一个 ...

  2. Java String类源码阅读笔记

    文章目录 一.前置 二.String类源码解析 1.String类继承关系 2.成员变量 3.构造方法 4.长度/判空 5.取字符 6.比较 7.包含 8.hashCode 9.查询索引 10.获取子 ...

  3. java中talent-aio_talent-aio源码阅读小记(一)

    近来在oschina上看到一个很火的java 即时通讯项目talent-aio,恰巧想了解一下这方面的东西,就阅读了一下项目的源码,这里对自己阅读源码后的一些心得体会做一下备忘,也希望能够对其他项目中 ...

  4. java工具类源码阅读,java学习日记第二天(实用的工具类和源码解析一Arrays)

    本帖最后由 三木猿 于 2020-9-18 11:17 编辑 每日名言 学者须先立志.今日所以悠悠者,只是把学问不曾做一件事看,遇事则且胡乱恁地打过了,此只是志不立. --朱熹 工作中经常会用到一些工 ...

  5. java java.lang.enum_源码阅读-java基础-java.lang.Enum

    1.引言 枚举类型是 JDK 5 之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量.相比与常量(public static final定义),在安全性.指意性.可读性方面更胜一筹.另外它可 ...

  6. java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO

    来源:木杉的博客 , imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/ Java传统IO是不支持中断的, ...

  7. Android Framework源码阅读计划(2)——LocationManagerService.java

    Android Framework源码阅读计划 Android Framework源码阅读计划(1)--LocationManager.java Android Framework源码阅读计划(2)- ...

  8. 面试官系统精讲Java源码及大厂真题 - 30 AbstractQueuedSynchronizer 源码解析(上)

    30 AbstractQueuedSynchronizer 源码解析(上) 不想当将军的士兵,不是好士兵. 引导语 AbstractQueuedSynchronizer 中文翻译叫做同步器,简称 AQ ...

  9. 【java】java JUC 同步器框架 AQS AbstractQueuedSynchronizer源码图文分析

    1.概述 转载:JUC锁: 锁核心类AQS详解 AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore ...

最新文章

  1. poj 2362 Square
  2. rs485调试软件_5种RS485切换方向的方法及优劣势分析
  3. 配置深度学习环境的重要资料
  4. 数据库连接池为什么要用threadlocal呢?(不用会怎样?)
  5. Map字符串类型去掉空格处理
  6. mysql中基本的DDL语句(关注一下,以后会继续更新喔!)
  7. python xlutils函数,python3:xlrd、xlwt、xlutils处理excel文件
  8. Educational Codeforces Round 30 A[水题/数组排序]
  9. 会计云课堂实名认证后怎么更改_会计云课堂网上听课步骤详解
  10. 揭开CSS的绝对定位真实的面纱(二)
  11. 串灯控制盒去掉怎么接_彩灯控制器坏了怎么办
  12. 2021-09-10 网安实验-文件修复-BMP图片隐写
  13. 请教苹果虚拟机自动配置序列号ID脚本
  14. “金三银四“,敢不敢“试”?
  15. 如何删除2345SafeCenterSvc
  16. 计算机组装维护教学工作总结,计算机组装与维护教师工作总结_2
  17. 如何修改美食大战老鼠服务器,《美食大战老鼠》联运区组停止运营公告
  18. CINTA:同构,同态与商群
  19. 05【React再造之旅】从零实现一个React(下)
  20. 【C#】int与int?

热门文章

  1. TSQL--查找连续登陆用户
  2. 关于解决MyEclipse的耗内存的办法
  3. 从HP收购ArcSight看SIEM/MSS市场现状与格局【9月17日更新】
  4. (转贴) ArcIMS初级教程(1)
  5. ubuntu下安装MySQL8.0
  6. Django的认证系统(auth)
  7. BZOJ5336 TJOI2018 party 【状压DP】*
  8. SpringMVC学习记录二——非注解和注解的处理器映射器和适配器
  9. 【经典】Noip动态规划
  10. HTTP协议扫盲(一)HTTP协议的基本概念和通讯原理