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

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

condition的用法

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

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

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
// if(xxxx)判断不满足条件,等待,释放锁
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
// 做完事情了,通知condition上等待的开始抢占
condition.signal();
} finally {
lock.unlock();
}
}

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

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状态的等待队列节点
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加本线程到等待队列尾部
Node node = addConditionWaiter();
// 释放锁,唤醒同步队列中的后继节点
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果已经在同步队列中了,说明被成功sign唤醒
while (!isOnSyncQueue(node)) {
// 阻塞挂起
LockSupport.park(this);
// 确认是否需要中断时就退出
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理已经不是CONDITION状态的等待队列节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

唤醒过程signal()

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

if (!isHeldExclusively())
throw new IllegalMonitorStateException();

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

Node first = firstWaiter;
if (first != null)
doSignal(first);

3.用CAS清除CONDITION状态

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

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

Node p = enq(node);

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

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

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

do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} 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. CM311-1a(S905L3系列)玩转桌面

    那安装了ambian后,玩转桌面(安装GUI桌面环境)有没有可能呢?那肯定啊!那桌面有什么用?当然有用,多一种玩法,可以写写代码,上网冲浪,学习linux语法什么的.而且单主机只要40左右,想想看可以 ...

  2. 洛谷P4135 作诗(不一样的分块)

    题面 给定一个长度为 n n n 的整数序列 A A A ,序列中每个数在 [ 1 , c ] [1,c] [1,c] 范围内.有 m m m 次询问,每次询问查询一个区间 [ l , r ] [l, ...

  3. springboot配置(yami配置文件,JSR303数据校验,多环境配置)

    yami配置文件 YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写.在开发的这种语言时,YAML 的意思其实是:&q ...

  4. Go 语言入门 1-管道的特性及实现原理

    入坑 go 也快一年了,从今天开始会定期分享一下 Go 语言学习过程中的一些基础知识. go 语言中的管道, 主要是用于协程之间的通信, 比 UNIX 的管道更加轻量和易用. 我们先看一下管道的数据结 ...

  5. Java基础——01

    今日学习 2020-2-27 Java多态 多态性格式 /* 代码中体现多态性 其实就是一句话:父类指向子类对象 格式: 父类名称 对象名= new 子类名称(): 或者 接口名称 对象名 = new ...

  6. 【Traefik二次开发】服务 Service 开发

    Service 定义 https://doc.traefik.io/traefik/routing/services/ The Services are responsible for configu ...

  7. 如何使用Postman快速简单的调用快递物流平台快递鸟API接口

    前沿 快递鸟是一家聚合类的第三方快递物流平台,目前该平台提供的产品主要以API为主.由于API不能直观的看到产品效果,需要进行API对接联调成功后才能真实的看到产品的实际效果.但是如果一上来就写代码进 ...

  8. uniapp小程序新版授权登录

    1.授权按钮: <view> <button class='login-btn' type='primary' @click="bindGetUserInfo"& ...

  9. Toast 部分记录丢失问题处理

    在日常数据库运维过程中,我们可能会遇到类似以下的错误.该错误信息是由于部分tuple的 toast 字段丢失,导致数据无法访问.需要通过游标方式,将可以访问的数据备份出来. test=# create ...

  10. KDB_Database_Link 使用介绍

    kdb_database_link 是 KingbaseES 为了兼容oracle 语法而开发的跨数据库访问扩展,可用于访问KingbaseES, Postgresql , Oracle .以下分别介 ...