摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。

本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。

condition的用法

condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。

和直接用lock\unlock去做等待通知的区别在于,lock是不会释放锁的,但是利用的condition的await则可以,且唤醒后会自动重新拿回锁。

  1. Lock lock = new ReentrantLock();
  2. Condition condition = lock.newCondition();
  3. public void conditionWait() throws InterruptedException {
  4. lock.lock();
  5. try {
  6. // if(xxxx)判断不满足条件,等待,释放锁
  7. condition.await();
  8. } finally {
  9. lock.unlock();
  10. }
  11. }
  12. public void conditionSignal() throws InterruptedException {
  13. lock.lock();
  14. try {
  15. // 做完事情了,通知condition上等待的开始抢占
  16. condition.signal();
  17. } finally {
  18. lock.unlock();
  19. }
  20. }

也提供了一些支持中断、支持超时的等待方法

condition 和 object.wait/notify的区别

  1. object的wait依赖sync, 只能最多有一个等待队列。 而通过newCondition可以制造多个等待队列
  2. wait不支持中断,而condition支持
  3. condition支持等待特定时间

condition原理分析

超大原理流程图

  • await(), 简单来讲就是把当前线程放入condition的等待队列中,然后调用LockSupport.park拉起线程。如果被其他线程通过signal唤醒,则放入同步队列中竞争锁,竞争成功则返回,否则继续竞争。
  • signal方法,就是拿到condition的等待队列头节点,用cas修改节点状态,改成功则唤醒线程。但有可能被别人抢先,所以需要cas操作。

代码结构部分:

​ Lock提供了newCondition接口给外部锁调用

​ 而newCondition()返回的Condition是一个接口

​ 这个接口的实现类是ConditionObject,放在AQS抽象类的内部类中

原理实现部分

等待队列

  • 每个condition都有一个属于自己的等待队列
  • 每次调用condition.await, 就插入到等待队列尾部
  • 等待队列插入封装线程的节点时不需要在尾部CAS, 因为必须先获取锁,才能调用await,因此不用CAS竞争
  • 每个Lock只有一个同步队列(用于lock()时阻塞和竞争用), 但是可能会有多个等待队列(用于condition的await)

等待过程

  1. 添加线程到condition的等待队列尾部
  2. 释放占用的锁,并唤醒同步队列的后继节点
  3. 此时肯定不在aqs的同步队列中了, 用park方法进入阻塞状态
  4. 被唤醒,唤醒时可能是通过sign()被人放入了同步队列, 也可能是被中断唤醒,因此要做checkInterruptWhileWaiting检查看是否继续, 如果同意继续,就继续睡眠,直到进入同步队列
  5. 尝试acquireQueued竞争和抢占state同步状态
  6. 退出前,顺带用unlinkCancelledWaiters清理已经不是CONDITION状态的等待队列节点
  1. public final void await() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. // 添加本线程到等待队列尾部
  5. Node node = addConditionWaiter();
  6. // 释放锁,唤醒同步队列中的后继节点
  7. int savedState = fullyRelease(node);
  8. int interruptMode = 0;
  9. // 如果已经在同步队列中了,说明被成功sign唤醒
  10. while (!isOnSyncQueue(node)) {
  11. // 阻塞挂起
  12. LockSupport.park(this);
  13. // 确认是否需要中断时就退出
  14. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  15. break;
  16. }
  17. // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
  18. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
  19. interruptMode = REINTERRUPT;
  20. // 清理已经不是CONDITION状态的等待队列节点
  21. if (node.nextWaiter != null)
  22. unlinkCancelledWaiters();
  23. if (interruptMode != 0)
  24. reportInterruptAfterWait(interruptMode);
  25. }

唤醒过程signal()

1.检查调用signal时,是否当前线程获取了锁,不是则抛异常

  1. if (!isHeldExclusively())
  2. throw new IllegalMonitorStateException();

2.获取condition队列中的第一个等待节点

  1. Node first = firstWaiter;
  2. if (first != null)
  3. doSignal(first);

3.用CAS清除CONDITION状态

  1. if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
  2. return false;

4.调用AQS的enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要CAS支撑?因为可能是共享的,即使获取了锁也需要竞争)

  1. Node p = enq(node);

5.移动入同步队列成功后(可能经历了几次CAS),再用unpark方法唤醒,那个线程就进入了上面代码中Park之后的部分了

  1. int ws = p.waitStatus;
  2. if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
  3. LockSupport.unpark(node.thread);

6.如果是signalAll方法,则等待队列中每个节点都执行一次signal方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过sign唤醒了)

  1. do {
  2. Node next = first.nextWaiter;
  3. first.nextWaiter = null;
  4. transferForSignal(first);
  5. first = next;
  6. } while (first != null);

点击关注,第一时间了解华为云新鲜技术~

详解AQS中的condition源码原理的更多相关文章

  1. 多图详解Go中的Channel源码

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码时14.4 chan介绍 package main import & ...

  2. 详解Go语言调度循环源码实现

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com/archives/448 本文使用的go的源码15.7 概述 提到"调度&q ...

  3. 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!

    Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...

  4. 多图详解Go的sync.Pool源码

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码时14.4 Pool介绍 总所周知Go 是一个自动垃圾回收的编程语言 ...

  5. Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

    package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.V ...

  6. 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

    前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙——HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...

  7. 【Java入门提高篇】Day22 Java容器类详解(五)HashMap源码分析(上)

    准备了很长时间,终于理清了思路,鼓起勇气,开始介绍本篇的主角——HashMap.说实话,这家伙能说的内容太多了,要是像前面ArrayList那样翻译一下源码,稍微说说重点,肯定会让很多人摸不着头脑,不 ...

  8. 【Java入门提高篇】Day21 Java容器类详解(四)ArrayList源码分析

    今天要介绍的是List接口中最常用的实现类——ArrayList,本篇的源码分析基于JDK8,如果有不一致的地方,可先切换到JDK8后再进行操作. 本篇的内容主要包括这几块: 1.源码结构介绍 2.源 ...

  9. Geth命令用法-参数详解 and 以太坊源码文件目录

    本文是对以太坊客户端geth命令的解析 命令用法 geth [选项] 命令 [命令选项] [参数-] 版本 1.7.3-stable 命令 account 管理账户 attach 启动交互式JavaS ...

随机推荐

  1. C#基础_VS常用快捷键

    Shift+End,Shift+Home,跳转到当前代码的行尾和行首. 1.窗口快捷键 记忆诀窍: 凡跟窗口挂上钩的快捷键必有一个W(Windows): Ctrl+W,W:  浏览器窗口 (浏览橱窗用 ...

  2. .NET 7 性能改进 -- 至今为止最快的.NET平台

    2022年8月31日 Stephen Toub 发布的关于 .NET 7 性能改进的博客, 核心主题是 .NET 7 速度很快. 这篇博客非常的长,我尝试将它拷贝到Word 里,拷贝的时间都花了几分钟 ...

  3. LibreCAD常用命令

    目录 常见命令 常见命令 .text_center { text-align: center } \3cp>.text_left { } 动作命令 命令 绘制直线 相对坐标系 @长度<角度 ...

  4. Kingbase V8R6存储过程变量数据导出到操作系统文件

    Kingbase V8R6存储过程变量数据导出到操作系统文件 说明: KingbaseES V8R6如何将自定义过程中的变量数据导出到操作系统文件中. 本次案例数据库版本: test=# select ...

  5. KingbaseES V8R6集群维护之--修改数据库服务端口案例

    ​ 案例说明: 对于KingbaseES数据库单实例环境,只需要修改kingbase.conf文件的'port'参数即可,但是对于KingbaseES V8R6集群中涉及到多个配置文件的修改,并且在应 ...

  6. Jsoup爬取网上数据完成翻译

    Jsoup使用 首先进入Jsoup下载jar包 然后打开IDEA创建一个普通的java项目 在项目结构里创建 lib 目录 但是我们这样并不能直接进行使用 需要添加路径 右键点击 然后添加路径 选择模 ...

  7. 4、StringBuilder类

    StringBuilder类 一个可变的字符序列,此类提供一个与StringBuffer 兼容的 API,但不保证同步(StringBuilder 不是线程安全). 该类被设计用作 StringBuf ...

  8. Centos7搭建 KVM 环境

    注意:使用KVM管理虚拟机分命令行和图形化界面两种方式,这里最后一步安装的工具是图形化管理工具 安装前准备 验证CPU是否支持KVM 如果结果中有vmx(Intel)或svm(AMD)字样,就说明CP ...

  9. 第六章:Django 综合篇

    前面五章,已经将Django最主要的五大系统介绍完毕,除了这些主要章节,还有很多比较重要的内容,比如开发流程相关.安全.本地化与国际化.常见工具和一些框架核心功能.这些内容的篇幅都不大,但整合起来也是 ...

  10. kibana安装安装插件

    命令语法:bin/kibana-plugin install <package name or URL> 当您指定的插件名没有带 URL,插件工具将会尝试去下载 Elastic 官方插件. ...