Condition 源码解析

Condition 源码解析

文章目录

  • Condition 源码解析
    • 一、Condition
    • 二、Condition 源码解读
      • 2.1. lock.newCondition() 获取 Condition 对象
      • 2.2. condition.await() 阻塞过程
      • 2.3. condition.signal() 唤醒过程
      • 2.4. condition.await() 被唤醒后
    • 三、总结

一、Condition

在并发情况下进行线程间的协调,如果是使用的 synchronized 锁,我们可以使用 wait()/notify() 进行唤醒,如果是使用的 Lock 锁的方式,则可以使用 Condition 进行针对性的阻塞和唤醒,相较于 wait()/notify() 使用起来更灵活。那么 Condition 是如何实现线程的等待和唤醒的呢,本文通过解析Condition 的源码进行理解。

在进行源码分析前,先通过一个案例看下 Condition 是如何使用的

public class Test {public synchronized static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {lock.lock();System.out.println("线程1开始等待!");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1被唤醒继续执行结束!");lock.unlock();}, "1").start();new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}lock.lock();System.out.println("开始唤醒线程!");condition.signal();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2执行结束!");lock.unlock();}, "2").start();}
}

运行之后,可以看到下面日志:
在这里插入图片描述

由于在第一个线程中,使用的 condition.await() 因此当前线程会被阻塞挂起,而第二个线程,在 1s 后进行了 condition.signal() 操作,因此第一个线程会被唤醒继续执行。这里可以发现,第一个线程阻塞时锁并没有释放,而第二个线程在1s后也成功拿到锁了,所以表明在 condition.await() 时会自动释放当前锁,这点和 wait() 相同,在第二个线程进行了 condition.signal() 操作,第一个线程并没有继续向下执行,而是等待第二个线程处理完才会继续执行,由此可以表明被唤醒的线程会重新获取锁,成功获取锁后继续执行。

下面通过源码看下 Condition 是如何实现的等待唤醒。

二、Condition 源码解读

2.1. lock.newCondition() 获取 Condition 对象

首先看下在使用 lock.newCondition() 获取一个Condition 对象时,具体做了什么,这里以 ReentrantLock 为例,进入到 ReentrantLocknewCondition() 方法中,又执行了 SyncnewCondition() 方法,再进去就会发现其实是 new 了一个 ConditionObject 类对象:
在这里插入图片描述

下面点到这个类中,可以看到其实是 AQS 下的一个子类:
在这里插入图片描述

2.2. condition.await() 阻塞过程

了解到 Condition 的对象后,可以看到是 AQS 下的一个子类,那下面其他的方法也肯定依赖于 AQS ,下面看下 condition.await() 方法,点到 await() 方法中:

在这里插入图片描述

其中 addConditionWaiter() 则是将自己加入到 AQS的队列中,并获取到当前线程所在的 Node ,这里注意下 Node 的状态是 Node.CONDITION 也就是 -2,后面会依赖于该状态。

在这里插入图片描述在这里插入图片描述

再回到 await() 方法继续向下看,接着使用了 fullyRelease() 方法传入了当前的 Node ,这里的 fullyRelease() 方法主要做了释放当前线程锁的操作,可以看到又调用了 AQSrelease() 进行释放资源,也就是释放了当前所持有的锁。
在这里插入图片描述

回到 await() 方法中,当释放锁后,下面进入到了 while 循环中,通过查看 isOnSyncQueue() 方法,可以看到是符合while的条件也就可以进入到循环中:
在这里插入图片描述
在这里插入图片描述

在循环中可以明显的看到 LockSupport.park(this) ,将当前线程进行了阻塞。

2.3. condition.signal() 唤醒过程

上面已经看到线程被阻塞了,如果需要被唤醒则需要通过condition.signal(),这个方法是如何唤醒的呢?

下面来到 AbstractQueuedSynchronizer 类的 signal() 方法中:
在这里插入图片描述

主要执行了 doSignal() 方法,再点到 doSignal() 中,可以看到这里开启了一个循环,对链表的每一个元素都进行了 transferForSignal() 操作,这里也比较好理解,就是要唤醒等待中的线程。

在这里插入图片描述

下面点到 transferForSignal() 中,看下对每个 Node 都做了什么操作。点进去之后也比较好理解,如果状态是 Node.CONDITION 也就是 -2,刚才在解读 await() 方法时就提到这个状态了,这里正好形成了呼应,下面有个非常显眼的操作 LockSupport.unpark(node.thread) 直接唤醒了目标线程。也就是唤醒了 2.2 中的最后一步操作。

在这里插入图片描述

2.4. condition.await() 被唤醒后

await() 方法中的 LockSupport.park(this) 被唤醒后,继续向下执行,下面会判断下当前线程有没有被打断,如果没被打断则 break 终止循环继续执行。
在这里插入图片描述
在这里插入图片描述

下面会使用 AQSacquireQueued() 方法,将先进入队列的线程进行抢占锁资源,如果成功获取锁后就会继续执行,如果抢占失败则继续被挂起阻塞。

在这里插入图片描述
在这里插入图片描述

三、总结

通过上面的源码分析,应该对 Condition 有了新的理解和掌握,在源码中好多地方都使用了 CAS ,因此当竞争资源非常激烈时, Lock 的性能要远远优于 synchronized

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/232077.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『亚马逊云科技产品测评』活动征文| 基于etcd实现服务发现

提示:授权声明:本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 Developer Centre, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道 背景 etcd 是一个分布式 Key-Value 存储系统&#xff0…

Android修行手册 - 一篇文章从0到1搞一个Android Studio插件。

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分…

Spine深入学习 —— 换装

Spine深入学习————换装 数据对象和实例对象的关系与区别 数据对象是无状态的,可在任意数量的骨架实例间共用。有对应实例数据的数据对象类名称以“Data”结尾,没有对应实例数据的数据对象则没有后缀,如附件、皮肤及动画。 实例对象有许…

大数据Hadoop-HDFS_元数据持久化

大数据Hadoop-HDFS_元数据持久化 (1)在HDFS第一次格式化后,NameNode(即图中的主NameNode)就会生成fsimage和editslog两个文件; (2)备用NameNode(即图中的备NameNode&…

什么是供应链攻击?

随着企业越来越依赖技术、连接性和第三方,供应链攻击变得越来越普遍。这些攻击旨在通过供应商和业务合作伙伴损害公司。 供应链攻击可能对企业和组织构成重大威胁,损害其安全以及向客户提供的产品和服务的安全。 在本文中,我们将探讨供应链…

java学习part23异常try catch

124-异常处理-异常的概述与常见异常的举例_哔哩哔哩_bilibili 1.异常 2.try catch 3.finally 类似golang的defer 一定执行的语句

数据结构 -- 图论之最小生成树

目录 1.最小生成树算法 1.Kruskal算法 2.Prim算法 1.最小生成树算法 定义:最小生成树算法:连通图有n个顶点组成,那么此时的图的每一个点都能相互连接并且边的个数为n-1条,那么此时该图就是最小生成树. 下面量算法有几个共同的特点: 1.只能使用图中权值最小的边来构造生成树 …

Javaweb之Vue组件库Element案例的详细解析

4.4 案例 4.4.1 案例需求 参考 资料/页面原型/tlias智能学习辅助系统/首页.html 文件,浏览器打开,点击页面中的左侧栏的员工管理,如下所示: 需求说明: 制作类似格式的页面 即上面是标题,左侧栏是导航&…

单片机----串行通信

目录 串行通信的两种方式 串行通信的传输模式 串行通信的错误校验 1.奇偶校验 2.代码和校验 3.循环冗余码校验 串行口结构 串行口控制寄存器SCON 特殊功能寄存器PCON 串行口的4种工作方式 方式0: (1)方式0的发送过程 &#xff0…

【Flutter】graphic图表实现tooltip一段时间后自动隐藏

概述 graphic图表中提供了自定义tooltip的事件,可通过selections中on和clear配置手势选项和可识别设备,默认情况下tooltip需要双击隐藏,但这并不符合我们的需求。通过调研发现,若想实现tooltip隔几秒后隐藏,可通过Str…

【刷题】树的遍历

层序遍历 层序遍历需要用到广度有限搜索,也就是需要队列 1.将根节点加入队列、 2.如果队列不为空,就得到队列的长度,对队列中现有的元素进行访问并从队列中删除,并将其子节点加入到队列中 102. 二叉树的层序遍历 给你二叉树的根…

算法基础之字符串哈希

字符串哈希 核心思想&#xff1a;用p(131或者13331)进制数储存字符串每一位数的hash值 L—R的哈希值 h[R]-h[L-1]*PR-L1 哈希值很大—>modQ(264)变小 用unsigned long long 存 (出界) #include<iostream>using namespace std;typedef unsigned long long ULL;co…