详解ConditionObject

目录

什么是ConditionObject:

ConditionObject的应用:

源码分析await()方法:(前置)

进入await()方法:

进入addConditionWaiter()方法:

unlinkCancelledWaiters()方法

fullyRelease(node)方法:

源码分析signal()方法:

主体了解一下唤醒的流程:

signal():方法

doSignal(first);方法

transferForSignal(first)方法:

源码分析await()方法:(后置)

方法transferAfterCancelledWait(node):

isOnSyncQueue()方法

acquireQueued(node, savedState)方法

reportInterruptAfterWait(int interruptMode)方法:

源码分析awaitNanos&signalAll方法

进入方法awaitNanos():

宏观理解方法实现

signalAll方法()


什么是ConditionObject:

在synchronized里面我们可以使用wait()方法挂起线程,notify()方法唤醒线程,ReentrantLock也提供和了自己专属的方式达到这个要求。那就是await()方法和signal()方法。

注意在使用await()方法和signal()方法的线程必须拥有锁。

ConditionObject的应用:

我们的流程的分析:

首先我们的线程1开始时获取到锁资源吗,之后调用await()方法,将当前获得到锁的线程1挂起,这时我们的主线程开始获取锁,之后让主线程将线程1唤醒,之后线程1结束锁资源。大家可以直接运行一下代码看一下效果。

public class Show_Contidion {private static ReentrantLock lock = new ReentrantLock();
​public static void main(String[] args)   {Condition condition = lock.newCondition();Thread t1 = new Thread(() -> {lock.lock();//开始获取到System.out.println("线程1获取到锁资源");try {//我是先睡眠,所以现在主线程要等到可以执行await()时才可以获取资源Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}try {condition.await();//将线程挂起} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程一再次获取到锁");});t1.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}
​lock.lock();System.out.println("主线程获取到锁");condition.signal();lock.unlock();}
}

源码分析await()方法:(前置)

首先我们在前置分析中,这是分析到线程挂起。后续的在后置中讲解,接着读。

看源码时发现对于单项链表,我们设置一个哨兵节点是很有必要的,这对于我们在写算法与数据结构时也有帮助。

进入await()方法:

在进入后我们发现这个方法是抽象的方法,接着直接去看他的实现类:

 void await() throws InterruptedException;

在前置方法里面我们先只是看到线程挂起的过程,在线程挂起之后的那部分我们在后续的后置部分讲解。

public final void await() throws InterruptedException {//判断是否出现中断,如果是中断直接抛出异常if (Thread.interrupted())throw new InterruptedException();//讲节点添加进condition单向链表Node node = addConditionWaiter();//讲持有的锁全部释放int savedState = fullyRelease(node);//设置中断的modeint interruptMode = 0;//如果不再AQS队列是,开始挂起线程 //因为是先释放的锁资源,如果在释放之后,其他的线程直接讲当前线程唤醒,那么他就直接进入AQS队列直接排队,准备抢占线程,那样的话,无需再次挂起线程。while (!isOnSyncQueue(node)) {LockSupport.park(this);//...后置代码在接下来讲解}
}
进入addConditionWaiter()方法:
private Node addConditionWaiter() {//获取队列队列中的最后的节点Node t = lastWaiter;// 如果最后的节点不是null并且节点的状态不是-2  证明这个节点不是有效的节点if (t != null && t.waitStatus != Node.CONDITION) {//进入方法,直接将节点取消unlinkCancelledWaiters();//讲t设置为新的尾节点t = lastWaiter;}//设置节点信息,将状态设置为-2Node node = new Node(Thread.currentThread(), Node.CONDITION);//如果t==null  证明condition单向链表里面没有任何的元素,直接设置当前节点为first节点if (t == null)firstWaiter = node;else//将节点设置再尾节点后t.nextWaiter = node;//重新设置尾节点lastWaiter = node;//返回封装好的节点,便于接下来的操作。return node;}
unlinkCancelledWaiters()方法
private void unlinkCancelledWaiters() {//获取队列的开头节点Node t = firstWaiter;Node trail = null;//如果节点不是nullwhile (t != null) {//节点next为t的下一个节点Node next = t.nextWaiter;//t的线程也是不正常的if (t.waitStatus != Node.CONDITION) {//设置他直接指向null,方便回收t.nextWaiter = null;//如果头节点就是nullif (trail == null)//讲头节点设置为他的下一个节点firstWaiter = next;else//将他的指针指向他下一个的下一个,讲失效的节点直接清除。trail.nextWaiter = next;if (next == null)lastWaiter = trail;}else//讲rail设置为ttrail = t;//设置t为直接的next;t = next;}
}
fullyRelease(node)方法:
 final int fullyRelease(Node node) {boolean failed = true;try {int savedState = getState();//核心就是这个代码,还是释放锁的代码,但是这里面释放的值就是state的值。直接设置为0;if (release(savedState)) {failed = false;//记录了未释放之前的state的值(注意!!!!!!)return savedState;} else {//失败就是使用这个方式必须持有锁。没持有的化,不需要挂起throw new IllegalMonitorStateException();}} finally {if (failed)//如果释放锁失败,将节点设置为取消。还是你没有锁资源,就是用方法await().创建的节点只能是取消node.waitStatus = Node.CANCELLED;}}

源码分析signal()方法:

将等待时间最长的线程(如果存在)从此条件的等待队列移动到拥有锁的等待队列这是源码里的话,证明我们的唤醒顺序是从头开始的。

主体了解一下唤醒的流程:
  1. 首先检验是否是当前线程,如果是的话才能进行接下来的操作

  2. 唤醒时从第一个节点开始唤醒 如果第一个节点就是null证明当前condition单向链表里面没有任何的节点,无需唤醒。

  3. 开始唤醒之后,检查要唤醒的节点状态是否正常,将当前唤醒的节点的nextWaiter这是为null,便于再唤醒后回收这个节点

  4. 唤醒时将节点放置到AQS队列,再放置到AQS队列时需要进行健壮性分析,如果他的前置接点出现问题,将现在这个新的节点直接唤醒。

signal():方法
 public final void signal() {//查看是否是当前的线程,如果不是直接抛出异常if (!isHeldExclusively())throw new IllegalMonitorStateException();//获取第一个节点,开始唤醒Node first = firstWaiter;if (first != null)  //但是被唤醒的节点不能是nulldoSignal(first);//开始唤醒}
doSignal(first);方法
 private void doSignal(Node first) {do {//如果当前节点的下一个节点是null,也就是当前节点就是最后一个节点了if ( (firstWaiter = first.nextWaiter) == null)//设置最后节点为nulllastWaiter = null;,//如果不是,后面还是有节点的   将这个节点指针设置为null 便于垃圾回收first.nextWaiter = null;} while (//!transferForSignal(first)  //如果返回时false  继续判断,不然直接结束循环&&//   (first = firstWaiter) != null);//如果下一个节点不时null 继续循环}
transferForSignal(first)方法:
final boolean transferForSignal(Node node) {//开始将节点的状态从-2改为0  如果不能改的话就是节点已经取消  if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//表示将讲节点放到AQS队列失败return false;//到这里就是修改节点状态成功  p是AQS队列,就是将当前节点插入到p节点的后面Node p = enq(node);//获取p节点的状态int ws = p.waitStatus;//如果状态是1 证明节点已经取消  或者是再将p节点的ws设置为-1失败时if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//将线程唤醒,放置因为前置节点状态问题,当前节点无法唤醒的健壮性分析LockSupport.unpark(node.thread);return true;}

源码分析await()方法:(后置)

使用线程唤醒之后,首先我们需要知道他是如何背唤醒的,是中断唤醒还是signal()唤醒,还是先被signal()唤醒之后紧接着被中断。

只要是唤醒之后我们就是要保证我们的节点在AQS队列,并且在condition单向链表中将节点取消。

如果是REINTERRUPT需要重新设置中断标记位。

public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();Node node = addConditionWaiter();int savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {LockSupport.park(this);//经过上面的过程将线程挂起//状态是0  是signal唤醒  //状态是-1 中断唤醒, 那这个节点是不是再AQS队列里面呢:探讨//状态时1 背signal唤醒之后,背中断//checkInterruptWhileWaiting(node))之后节点一定在AQS中if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//如果不是直接最简单的signal()唤醒,直接跳出循环//如果中断的话break;}// 如果返回时true 证明在AQS排队时,线程再次中断,但是中断的状态不是THROW_IE,那就是证明是REINTERRUPTif (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) //如果节点的nextWaiter不是null,证明还是在condition单向链表里面unlinkCancelledWaiters();//将节点取消if (interruptMode != 0)//如果不是0,reportInterruptAfterWait(interruptMode);}
private int checkInterruptWhileWaiting(Node node) {return Thread.interrupted() ?//THROW_IE:被中断唤醒 -1//REINTERRUPT:背signal唤醒之后,被中断 1(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :0;//如果是零的话就是没有中断
}
方法transferAfterCancelledWait(node):
//如果返回时true 那就是直接被中断唤醒,如果是false那就是signal()唤醒的,之后中断,如果是singal()唤醒的,那就是一定在AQS里面,如果是中断唤醒的那就不在AQS中。
final boolean transferAfterCancelledWait(Node node) {//CAS操作将节点的状态由-2转化为0成功,那就是我的节点还是再condition单向链表中,那就是中断唤醒的if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {//将节点放置到AQS队列enq(node);//注意现在我们的节点即在AQS里,也在condition单向链表里面return true;}如果节点没有在AQS队列中,那就是证明现在还没有放置到AQS队列中,那就等一会,一直到成功放入while (!isOnSyncQueue(node))Thread.yield();//返回的是false  就是signal()唤醒的,之后中断return false;}
isOnSyncQueue()方法
final boolean isOnSyncQueue(Node node) {//判断节点是否在AQS队列中if (node.waitStatus == Node.CONDITION || node.prev == null)//如果节点的状态是-2证明节点在condition单向链表,或者是当前节点的前置接点是null,AQS队列里面由伪节点,不会出现前置节点为null,所以也是没在AQS队列return false;if (node.next != null) // 如果你的后置接点不为空,直接返回truereturn true;//最后就是从后往前遍历AQS队列,查看是否存在节点return findNodeFromTail(node);
}
acquireQueued(node, savedState)方法
//对于lock()方法里面的中断操作我们先忽略。
final boolean acquireQueued(final Node node, int arg) {//设置状态,当前未获取到锁资源boolean failed = true;try {boolean interrupted = false;//死循环,必须完成一件事 for (;;) {//查找当前节点的前驱节点final Node p = node.predecessor();//开始判断,如果我的前置节点就是伪节点(注意是伪节点)直接在词使用tryAcquire()尝试获取锁资源if (p == head && tryAcquire(arg)) {//如果成功,就设置一些头节点setHead(node);p.next = null; // help GC//设置已经获取锁资源failed = false;//返回的值是false.return interrupted;}//如果前置节点不是伪节点的话,if (shouldParkAfterFailedAcquire(p, node) &&//挂起线程parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
reportInterruptAfterWait(int interruptMode)方法:
private void reportInterruptAfterWait(int interruptMode)throws InterruptedException {//如果是中断唤醒,直接抛出异常if (interruptMode == THROW_IE)throw new InterruptedException();//如果是signal()唤醒之后在中断else if (interruptMode == REINTERRUPT)//再次设置中断标记selfInterrupt();
}

源码分析awaitNanos&signalAll方法

其实awaitNancos()就是带有时间的await方式,其实核心的思想还是和上面的思路几乎一致。

现在开始源码的分析:

long awaitNanos(long nanosTimeout) throws InterruptedException;
进入方法awaitNanos():

小编建议我们在学习源码时,还是要看一下源码的注释的,因为原作者的注释能够帮助我们学习他的思路和想法,小编在这里把注释放到文章中,帮助大家学习

implements timed condition wait.
If current thread is interrupted, throw InterruptedException.
Save lock state returned by getState.
Invoke release with saved state as argument, throwing IllegalMonitorStateException if it fails.
Block until signalled, interrupted, or timed out.
Reacquire by invoking specialized version of acquire with saved state as argument.
If interrupted while blocked in step 4, throw InterruptedException.
//---------------------------------------------------------------------------------------------
下面是直接翻译的版本
实现定时条件 wait。
如果当前线程被中断,则抛出 InterruptedException。
保存返回的 getState锁定状态。
以保存的状态作为参数调用 Invoke release ,如果失败,则抛出 IllegalMonitorStateException。
阻塞,直到发出信号、中断或超时。
通过调用具有保存状态作为参数的专用版本 来 acquire 重新获取。
如果在步骤 4 中被阻塞时被中断,则引发 InterruptedException
宏观理解方法实现
  1. 首先是看一下是否中断

  2. 之后看一下时间是否足够,时间长的话继续挂起,时间短或者<0开始唤醒

public final long awaitNanos(long nanosTimeout)throws InterruptedException {//如果中断,抛出异常if (Thread.interrupted())throw new InterruptedException();//获取节点信息,添加到condition单向链表Node node = addConditionWaiter();//获取节点的状态。int savedState = fullyRelease(node);//计算结束时间final long deadline = System.nanoTime() + nanosTimeout;//初始化参数int interruptMode = 0;//查看时候再AQS队列中while (!isOnSyncQueue(node)) {//如果时间结束,if (nanosTimeout <= 0L) {//将节点放置到AQS队列transferAfterCancelledWait(node);//直接跳出循环。break;}//如果还是有时间的,查看时间时候充足,时间还是充足的话继续将节点挂起if (nanosTimeout >= spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);//如果状态不是0,那就是被唤醒,直接跳出循环,因为我们在刚才讲解方法checkInterruptWhileWaiting()//时题到,只要时进入到checkInterruptWhileWaiting()节点一定进入到AQS队列if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;//在次计算时间nanosTimeout = deadline - System.nanoTime();}//如果前面返回的是true,就是在方放入队列是发生中断,但是中断还不是THROW_IE,那就是REINTERRUPTif (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;//将condition()里的无效节点取消if (node.nextWaiter != null)unlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);//返回剩余时间return deadline - System.nanoTime();
}
signalAll方法()
// 以do-while的形式,将Condition单向链表中的所有Node,全部唤醒并扔到AQS队列
private void doSignalAll(Node first) {// 将头尾都置位null~lastWaiter = firstWaiter = null;do {// 拿到next节点的引用Node next = first.nextWaiter;// 断开当前Node的nextWaiterfirst.nextWaiter = null;// 修改Node状态,扔AQS队列,是否唤醒!transferForSignal(first);// 指向下一个节点first = next;} while (first != null);
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【go从零单排】Random Numbers、Number Parsing

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 这里是引用 &#x1f4bb;代码 Random Numbers package mainimport ("fmt…

qt移植到arm报错动态库找不到

error while loading shared libraries: libAlterManager.so.1: cannot open shared object file: No such file or directory 通过设置环境变量 LD_LIBRARY_PATH就行了。 LD_LIBRARY_PATH是一个用于指定动态链接器在运行时搜索共享库的路径的环境变量。 例如&#xff1a; 前…

GoogleMIT:多智能体医疗决策框架MDAgents

|记昨日与国内某Top 1&2 医院科室老师及团队探讨技术、医学、信仰与责任而有感而发。 生成式基础大模型正在成为临床辅助甚至医学探索领域的宝贵工具。尽管我们在国内看到了很多企业或实验室联合医疗机构在如医疗记录生成、临床表型辅助诊疗、医疗知识问答交互、医院管理决…

【数据库】深入解析慢 SQL 的识别与优化策略

文章目录 什么是慢 SQL&#xff1f;慢 SQL 的危害如何检测分析慢 SQL使用 MySQL 慢查询日志利用 EXPLAIN 分析执行计划通过 Profiling 获取详细执行信息借助慢 SQL 收集分析平台 实际案例解析&#xff1a;600秒的慢 SQL 优化之旅问题描述初步分析优化步骤1. 优化 SQL 语句结构2…

高校大数据人工智能教学沙盘分享

大数据教学实训沙盘&#xff08;TipDM-SP&#xff09;是根据企业实际项目建设而成&#xff0c;并提供沙盘配套装置、软件以及教学实训资源。沙盘的作用主要有3个&#xff1a; 1、采集真实数据&#xff0c;解决教学中缺少真实数据的困扰&#xff1b; 2、形成从数据…

【C++】string模拟实现

各位读者老爷好&#xff0c;俺最近在学习string的一些知识。为了更好的了解string的结构&#xff0c;俺模拟实现了一个丐版string&#xff0c;有兴趣的老爷不妨垂阅&#xff01;&#xff01;&#xff01; 目录 1.string类的定义 2.模拟实现成员函数接口 2.1.constructor&am…

c_str()函数 string类型转换成char*类型 C++实现

问题&#xff1a;在 class 的构造函数中&#xff0c;如果我们在类中初始化了 char * 类型&#xff0c;在调用构造函数时&#xff0c;如果直接传入字符串( string )类型&#xff0c;编译器会提出如下警告&#xff1a; 想要消除这个警告&#xff0c;就需要将 string 类型的变量转…

【vue3文件上传同时出现两个提示框,一个提示成功,一个提示失败,一个是用写死的,一个是接口返回的】

文件上传同时出现两个提示框&#xff0c;一个提示成功&#xff0c;一个提示失败&#xff0c;一个是用写死的&#xff0c;一个是接口返回的 原因&#xff1a; 接口返回的是字符串code200" 把判断的code码改为字符串的就好了

选择哪种Facebook广告目标更有效

在Facebook广告投放中&#xff0c;广告目标的选择决定了投放效果和转化率&#xff0c;但很多人往往忽略了这一步的细节。今天&#xff0c;我们来一起看看Facebook广告目标有哪些&#xff0c;以及如何精准选择&#xff01; 1. 广告目标在投放中的重要性 广告目标不仅仅是一…

matlab实现主成分分析方法图像压缩和传输重建

原创 风一样的航哥 航哥小站 2024年11月12日 15:23 江苏 为了研究图像的渐进式传输技术&#xff0c;前文提到过小波变换&#xff0c;但是发现小波变换非常适合传输缩略图&#xff0c;实现渐进式传输每次传输的数据量不一样&#xff0c;这是因为每次变换之后低频成分大约是上一…

【缓存策略】你知道 Cache Aside(缓存旁路)这个缓存策略吗

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

稀疏视角CBCT重建的几何感知衰减学习|文献速递-基于深度学习的病灶分割与数据超分辨率

Title 题目 Geometry-Aware Attenuation Learning forSparse-View CBCT Reconstruction 稀疏视角CBCT重建的几何感知衰减学习 01 文献速递介绍 稀疏视角锥形束计算机断层扫描&#xff08;CBCT&#xff09;重建的几何感知学习方法 锥形束计算机断层扫描&#xff08;CBCT&a…

电子应用产品设计方案-3:插座式自动温控器设计

一、设计 插座式自动温控器作为一种便捷的温度控制设备&#xff0c;在日常生活和工业应用中发挥着重要作用。它能够根据环境温度的变化自动控制连接设备的电源通断&#xff0c;实现对温度的精确调节和节能控制。本设计旨在提供一种功能强大、易于使用、安全可靠的插座式自动温控…

机器学习—神经网络的Softmax输出

为了建立一个能进行多类分类的神经网络&#xff0c;将采用Softmax回归模型&#xff0c;把它放入神经网络的输出层&#xff0c;如何实现&#xff1f; 当我们用两门课做手写数字识别的时候&#xff0c;我们使用这种架构的神经网络&#xff0c;如果你现在想用十个类进行手写数字分…

web——sqliabs靶场——第五关——报错注入和布尔盲注

这一关开始上强度了&#xff0c;不回显东西了&#xff0c;又要学到新的东西了 发现它没有正确的回显&#xff0c;学到了新知识&#xff0c;报错注入 报错注入 什么是报错注入&#xff1a; MySQL提供了一个 updatexml() 函数&#xff0c;当第二个参数包含特殊符号时会报错&am…

【JavaScript】LeetCode:86-90

文章目录 86 只出现一次的数字87 颜色分类88 下一个排列89 寻找重复数90 前K个高频元素 86 只出现一次的数字 异或x ^ x 0&#xff0c;x ^ 0 x&#xff0c;相同为0&#xff0c;相异为1&#xff0c;且满足交换律。例如&#xff1a;[4, 1, 2, 1, 2] > 1 ^ 1 ^ 2 ^ 2 ^ 4 0 …

CSS回顾-基础知识详解

一、引言 在前端开发领域&#xff0c;CSS 曾是构建网页视觉效果的关键&#xff0c;与 HTML、JavaScript 一起打造精彩的网络世界。但随着组件库的大量涌现&#xff0c;我们亲手书写 CSS 样式的情况越来越少&#xff0c;CSS 基础知识也逐渐被我们遗忘。 现在&#xff0c;这种遗…

Spring Boot编程训练系统:构建可扩展的应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

点云论文阅读-1-pointnet++

pointnet局限性&#xff1a;不能获取局部结构信息 作者提出pointnet需要解决的问题&#xff1a; 如何生成点云的分区&#xff08;需要保证每一个分区具有相似的结构&#xff0c;使学习算法的参数在局部共享&#xff09;如何通过一个局部特征学习算法抽象点云或局部特征 解决…

Summaries 总结

Goto Data Grid 数据网格 Summaries 摘要 Summary Types 摘要类型 Total Summary 总摘要 汇总总数 &#xff08;GridSummaryItem&#xff09; 将针对所有数据网格记录进行计算&#xff0c;并显示在视图页脚中。启用 View 的 OptionsView.ShowFooter 设置以显示视图页脚。 …