当前位置: 首页 > news >正文

深入浅出Java Condition 的await和signal机制(二)

Condition 的 await 方法

当调用condition.await()方法后会使当前获取锁的线程进入到等待队列,如果该线程能够从await()方法返回的话,一定是该线程获取了与 Condition 相关联的锁。
前面讲过了,Condition 只是一个接口,它的实现类为 ConditionObject,是 AQS 的子类。
ConditionObject 的 await 方法源码如下:
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 1. 将当前线程包装成Node,尾插入到等待队列中 Node node = addConditionWaiter(); // 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点 int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { // 3. 当前线程进入到等待状态 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 4. 自旋等待获取到同步状态(即获取到lock) if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); // 5. 处理被中断的情况 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
代码的主要逻辑请看注释。当前线程调用condition.await()方法后,会释放 lock 然后加入到等待队列,直到被signal/signalAll方法唤醒。
怎样将当前线程添加到等待队列?
调用 addConditionWaiter 方法会将当前线程添加到等待队列中,源码如下:
private Node addConditionWaiter() { Node t = lastWaiter; if (t != null && t.waitStatus != Node.CONDITION) { //将不处于等待状态的节点从等待队列中移除 unlinkCancelledWaiters(); t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); //尾节点为空 if (t == null) //将首节点指向node firstWaiter = node; else //将尾节点的nextWaiter指向node节点 t.nextWaiter = node; //尾节点指向node lastWaiter = node; return node; }
首先将 t 指向尾节点,如果尾节点不为空并且它的waitStatus!=-2(-2 为 CONDITION,表示正在等待 Condition 条件),则将不处于等待状态的节点从等待队列中移除,并且将 t 指向新的尾节点。然后将当前线程封装成 waitStatus 为-2 的节点追加到等待队列尾部。如果尾节点为空,则表明队列为空,将首尾节点都指向当前节点。
如果尾节点不为空,表明队列中有其他节点,则将当前尾节点的 nextWaiter 指向当前节点,将当前节点置为尾节点。
简单总结一下,这段代码的作用就是通过尾插入的方式将当前线程封装的 Node 插入到等待队列中,同时可以看出,Condtion 的等待队列是一个不带头节点的链式队列不带头节点是指在链表数据结构中,链表的第一个节点就是实际存储的第一个数据元素,而不是一个特定的"头"节点,该节点不包含实际的数据。
1)不带头节点的链表:
  • 链表的第一个节点就是第一个实际的数据节点。
  • 当链表为空时,头引用(通常称为 head)指向 null。
2)带头节点的链表:
  • 链表有一个特殊的节点作为链表的开头,这个特殊的节点称为头节点。
  • 头节点通常不存储任何实际数据,或者它的数据字段不被使用。
  • 无论链表是否为空,头节点总是存在的。当链表为空时,头节点的下一个节点指向 null。
  • 使用头节点可以简化某些链表操作,因为不必特殊处理第一个元素的插入和删除。
1)不带头节点的链表
public class Node { public int data; public Node next; public Node(int data) { this.data = data; this.next = null; } } public class LinkedListWithoutHead { public Node head; public void insert(int value) { Node newNode = new Node(value); if (head == null) { head = newNode; } else { Node temp = head; while (temp.next != null) { temp = temp.next; } temp.next = newNode; } } }
2)带头节点的链表
public class NodeWithHead { public int data; public NodeWithHead next; public NodeWithHead(int data) { this.data = data; this.next = null; } } public class LinkedListWithHead { private NodeWithHead head; public LinkedListWithHead() { head = new NodeWithHead(-1); // 初始化头节点 } public void insert(int value) { NodeWithHead newNode = new NodeWithHead(value); NodeWithHead temp = head; while (temp.next != null) { temp = temp.next; } temp.next = newNode; } }
释放锁的过程
将当前节点插入到等待对列之后,会使当前线程释放 lock,由 fullyRelease 方法实现,源码如下:
final int fullyRelease(Node node) { //释放锁失败为true,释放锁成功为false boolean failed = true; try { //获取当前锁的state int savedState = getState(); //释放锁成功的话 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) //释放锁失败的话将节点状态置为取消 node.waitStatus = Node.CANCELLED; } }
这段代码也很容易理解,调用 AQS 的模板方法 release 释放 AQS 的同步状态并且唤醒在同步队列中头节点的后继节点引用的线程,如果释放成功则正常返回,若失败的话就抛出异常。
怎么从await方法中退出
现在回过头再来看 await 方法,其中有这样一段逻辑:
while (!isOnSyncQueue(node)) { // 3. 当前线程进入到等待状态 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; }
isOnSyncQueue 方法用于判断当前线程所在的 Node 是否在同步队列中。
如果当前节点的 waitStatus=-2,说明它在等待队列中,返回 false;如果当前节点有前驱节点,则证明它在 AQS 队列中,但是前驱节点为空,说明它是头节点,而头节点是不参与锁竞争的,也返回 false。如果当前节点既不在等待队列中,又不是 AQS 中的头节点且存在 next 节点,说明它存在于 AQS 中,直接返回 true。看一下同步队列与等待队列的关系图:
当线程第一次调用 condition.await 方法时,会进入到这个 while 循环,然后通过LockSupport.park(this)使当前线程进入等待状态,那么要想退出 await,第一个前提条件就是要先退出这个 while 循环,出口就只两个地方:
  1. 走到 break 退出 while 循环;
  2. while 循环中的逻辑判断为 false。
出现第 1 种情况的条件是,当前等待的线程被中断后代码会走到 break 退出,第 2 种情况是当前节点被移动到了同步队列中(即另外一个线程调用了 condition 的 signal 或者 signalAll 方法),while 中逻辑判断为 false 后结束 while 循环。
总结一下,退出 await 方法的前提条件是当前线程被中断或者调用 condition.signal 或者 condition.signalAll 使当前节点移动到同步队列后
当退出 while 循环后会调用acquireQueued(node, savedState),该方法的作用是在自旋过程中线程不断尝试获取同步状态,直到成功(线程获取到 lock)。这样也说明了退出 await 方法必须是已经获得了 condition 引用(关联)的 lock。await 方法示意图如下:
如图,调用 condition.await 方法的线程必须是已经获得了 lock 的线程,也就是当前线程是同步队列中的头节点。调用该方法后会使得当前线程所封装的 Node 尾插入到等待队列中。
超时机制的支持condition 还额外支持超时机制,使用者可调用 awaitNanos、awaitUtil 这两个方法,实现原理基本上与 AQS 中的 tryAcquire 方法如出一辙。不响应中断的支持要想不响应中断可以调用condition.awaitUninterruptibly()方法,该方法的源码如下:
public final void awaitUninterruptibly() { Node node = addConditionWaiter(); int savedState = fullyRelease(node); boolean interrupted = false; while (!isOnSyncQueue(node)) { LockSupport.park(this); if (Thread.interrupted()) interrupted = true; } if (acquireQueued(node, savedState) || interrupted) selfInterrupt(); }
这段方法与上面的 await 方法基本一致,只不过减少了对中断的处理。
http://www.jsqmd.com/news/332648/

相关文章:

  • 必看!半导体工艺代工服务商+新工艺验证技术服务实力厂家汇总,性价比首选出炉
  • springboot乐淘购物系统的设计与实现 开题报告
  • Word通配符技巧:高效文档处理指南
  • 高端宝宝起名定制公司哪家靠谱值得推荐?
  • 计算机毕业设计之基于Python的疫情数据分析系统
  • 建议收藏:运维大佬都会用的Vim命令技巧
  • 收集知识≠知识,知识在脑中,工具只是辅助
  • 计算机毕业设计之springboot校园智能停车收费监控系统的设计与实现
  • 教育行业用百度UM搭建校务系统时,如何处理WORD通知中图片的格式兼容?
  • 2026年最新版 Bloodshed Dev C++下载与安装配置完整图文教程
  • AI市场分析:原圈科技揭秘企业如何赢得未来十年竞争
  • 运维系列【仅供参考-推荐】:为网站配置HTTPS(Nginx SSL证书设置)
  • DHCP简介
  • 风险周报 | 全球供应链风险事件汇总:多地发生火灾,车厘子等迎涨价潮!
  • 互联网站群管理时,百度UMEDITOR如何统一处理多站点WORD图片粘贴需求?
  • 期货与期权一体化平台结构边界定义实践指南
  • 全网最全 9个AI论文写作软件测评:研究生毕业论文+开题报告必备工具推荐
  • SpringMVC中百M大文件上传如何分块处理?
  • 大宗商品风险对冲系统监测方案设计与实施
  • 网页上SpringBoot如何支持百M大文件的分段上传?
  • 基于深度学习YOLOv12的美国硬币识别检测系统(YOLOv12+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)
  • 本科生必看!最强的AI论文平台 —— 千笔写作工具
  • <span class=“js_title_inner“>恒运昌科创板上市:募资15.6亿 市值258亿 第三季营收净利降46%</span>
  • 基于深度学习YOLOv11的美国硬币识别检测系统(YOLOv11+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)
  • 生肖起名/宝宝起名的专业公司哪家好?
  • 亲测好用 10个一键生成论文工具:自考毕业论文+开题报告高效写作测评
  • 基于深度学习YOLOv12的扑克牌识别检测系统(YOLOv12+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)
  • 基于深度学习YOLOv12的野生动物识别检测系统(YOLOv12+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)
  • 大模型颠覆行业?那我们如何驾驭大模型把握住行业风口呢?
  • SEW变频器MCV40A0750-503-4-0T 08274851