CompletableFuture**应用源码分析(三)

2.3 CompletableFuture源码分析

CompletableFuture的源码内容特别多。不需要把所有源码都看了,更多的是要掌握整个CompletableFuture的源码执行流程,以及任务的执行时机。
从CompletableFuture中比较简单的方法作为分析的入口,从而掌握整体执行的流程。

2.3.1 当前任务执行方式

将任务和CompletableFuture封装到一起,再执行封住好的具体对象的run方法即可

// 提交任务到CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable) {
// asyncPool:执行任务的线程池
// runnable:具体任务。
return asyncRunStage(asyncPool, runnable);
}
// 内部执行的方法
static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
// 对任务做非空校验
if (f == null) throw new NullPointerException();
// 直接构建了CompletableFuture的对象,作为最后的返回结果
CompletableFuture<Void> d = new CompletableFuture<Void>();
// 将任务和CompletableFuture对象封装为了AsyncRun的对象
// 将封装好的任务交给了线程池去执行
e.execute(new AsyncRun(d, f));
// 返回构建好的CompletableFuture
return d;
}
// 封装任务的AsyncRun类信息
static final class AsyncRun extends ForkJoinTask<Void> implements Runnable, AsynchronousCompletionTask {
// 声明存储CompletableFuture对象以及任务的成员变量
CompletableFuture<Void> dep;
Runnable fn;
// 将传入的属性赋值给成员变量AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
this.dep = dep;
this.fn = fn;
}
// 当前对象作为任务提交给线程池之后,必然会执行当前方法
public void run() {
// 声明局部变量
CompletableFuture<Void> d; Runnable f;
// 将成员变量赋值给局部变量,并且做非空判断
if ((d = dep) != null && (f = fn) != null) {
// help GC,将成员变量置位null,只要当前任务结束后,成员变量也拿不到引用。
dep = null; fn = null;
// 先确认任务没有执行。
if (d.result == null) {
try {
// 直接执行任务
f.run();
// 当前方法是针对Runnable任务的,不能将结果置位null
// 要给没有返回结果的Runnable做一个返回结果
d.completeNull();
} catch (Throwable ex) {
// 异常结束!
d.completeThrowable(ex);
}
}
d.postComplete();
}}
}

2.3.2 任务编排的存储&执行方式

首先如果要在前继任务处理后,执行后置任务的话。
有两种情况:
● 前继任务如果没有执行完毕,后置任务需要先放在stack栈结构中存储
● 前继任务已经执行完毕了,后置任务就应该直接执行,不需要在往stack中存储了。
如果单独采用thenRun在一个任务后面指定多个后继任务,CompletableFuture无法保证具体的执行顺序,而影响执行顺序的是前继任务的执行时间,以及后置任务编排的时机。

2.3.3 任务编排流程

// 编排任务,前继任务搞定,后继任务再执行
public CompletableFuture<Void> thenRun(Runnable action) {
// 执行了内部的uniRunStage方法,
// null:线程池,现在没给。
// action:具体要执行的任务
return uniRunStage(null, action);
}
// 内部编排任务方法
private CompletableFuture<Void> uniRunStage(Executor e, Runnable f) {// 后继任务不能为null,健壮性判断
if (f == null) throw new NullPointerException();
// 创建CompletableFuture对象d,与后继任务f绑定
CompletableFuture<Void> d = new CompletableFuture<Void>();
// 如果线程池不为null,代表异步执行,将任务压栈
// 如果线程池是null,先基于uniRun尝试下,看任务能否执行
if (e != null || !d.uniRun(this, f, null)) {
// 如果传了线程池,这边需要走一下具体逻辑
// e:线程池
// d:后继任务的CompletableFuture
// this:前继任务的CompletableFuture
// f:后继任务
UniRun<T> c = new UniRun<T>(e, d, this, f);
// 将封装好的任务,push到stack栈结构
// 只要前继任务没结束,这边就可以正常的将任务推到栈结构中
// 放入栈中可能会失败
push(c);
// 无论压栈成功与否,都要尝试执行以下。
c.tryFire(SYNC);
}
// 无论任务执行完毕与否,都要返回后继任务的CompletableFuture
return d;
}

 2.3.4 查看后置任务执行时机

任务在编排到前继任务时,因为前继任务已经结束了,这边后置任务会主动的执行

// 后置任务无论压栈成功与否,都需要执行tryFire方法
static final class UniRun<T> extends UniCompletion<T,Void> {
Runnable fn;
// executor:线程池
// dep:后置任务的CompletableFuture
// src:前继任务的CompletableFuture
// fn:具体的任务
UniRun(Executor executor, CompletableFuture<Void> dep,CompletableFuture<T> src, Runnable fn) {
super(executor, dep, src); this.fn = fn;
}
final CompletableFuture<Void> tryFire(int mode) {
// 声明局部变量
CompletableFuture<Void> d; CompletableFuture<T> a;
// 赋值局部变量
// (d = dep) == null:赋值加健壮性校验
if ((d = dep) == null ||
// 调用uniRun。
// a:前继任务的CompletableFuture
// fn:后置任务
// 第三个参数:传入的是this,是UniRun对象
!d.uniRun(a = src, fn, mode > 0 ? null : this))
// 进到这,说明前继任务没结束,等!
return null;
dep = null; src = null; fn = null;return d.postFire(a, mode);
}
}
// 是否要主动执行任务
final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
// 方法要么正常结束,要么异常结束
Object r; Throwable x;
// a == null:健壮性校验
// (r = a.result) == null:判断前继任务结束了么?
// f == null:健壮性校验
if (a == null || (r = a.result) == null || f == null)
// 到这代表任务没结束。
return false;
// 后置任务执行了没? == null,代表没执行
if (result == null) {
// 如果前继任务的结果是异常结束。如果前继异常结束,直接告辞,封装异常结果
if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
completeThrowable(x, r);
else
// 到这,前继任务正常结束,后置任务正常执行
try {
// 如果基于tryFire(SYNC)进来,这里的C不为null,执行c.claim
// 如果是因为没有传递executor,c就是null,不会执行c.claim
if (c != null && !c.claim())
// 如果返回false,任务异步执行了,直接return false
return false;// 如果claim没有基于线程池运行任务,那这里就是同步执行
// 直接f.run了。
f.run();
// 封装Null结果
completeNull();
} catch (Throwable ex) {
// 封装异常结果
completeThrowable(ex);
}
}
return true;
}
// 异步的线程池处理任务
final boolean claim() {
Executor e = executor;
if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
// 只要有线程池对象,不为null
if (e == null)
return true;
executor = null; // disable
// 基于线程池的execute去执行任务
e.execute(this);
}
return false;
}

前继任务执行完毕后,基于嵌套的方式执行后置。

// A:嵌套了B+C, B:嵌套了D+E
// 前继任务搞定,遍历stack执行后置任务
// A任务处理完,解决嵌套的B和C
final void postComplete() {
// f:前继任务的CompletableFuture
// h:存储后置任务的栈结构
CompletableFuture<?> f = this; Completion h;
// (h = f.stack) != null:赋值加健壮性判断,要确保栈中有数据
while ((h = f.stack) != null ||
// 循环一次后,对后续节点的赋值以及健壮性判断,要确保栈中有数据
(f != this && (h = (f = this).stack) != null)) {
// t:当前栈中任务的后续任务
CompletableFuture<?> d; Completion t;
// 拿到之前的栈顶h后,将栈顶换数据
if (f.casStack(h, t = h.next)) {
if (t != null) {
if (f != this) {
pushStack(h);
continue;
}
h.next = null; // detach
}
// 执行tryFire方法,
f = (d = h.tryFire(NESTED)) == null ? this : d;
}}
}
// 回来了 NESTED == -1
final CompletableFuture<Void> tryFire(int mode) {
CompletableFuture<Void> d; CompletableFuture<T> a;
if ((d = dep) == null ||
!d.uniRun(a = src, fn, mode > 0 ? null : this))
return null;
dep = null; src = null; fn = null;
// 内部会执行postComplete,运行B内部嵌套的D和E
return d.postFire(a, mode);
}

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

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

相关文章

python实操之网络爬虫介绍

一、什么是网络爬虫 网络爬虫&#xff0c;也可以叫做网络数据采集更容易理解。它是指通过编程向网络服务器&#xff08;web&#xff09;请求数据&#xff08;HTML表单&#xff09;&#xff0c;然后解析HTML&#xff0c;提取出自己想要的数据。 它包括了根据url获取HTML数据、解…

R.swift SwiftGen 资源使用指南

R.swift 和 SwiftGen 资源转换使用指南 R.swift &#xff08;原始代码会打包到项目&#xff1f;&#xff09; Pod platform :ios, 12.0 target LBtest do# Comment the next line if you dont want to use dynamic frameworksuse_frameworks!pod R.swift # pod SwiftGen, ~&g…

ConcurrentHashMap和HashMap的区别

什么是HashMap &#xff08;1&#xff09;HashMap 是基于 Map 接口的非同步实现&#xff0c;线程不安全&#xff0c;是为了快速存取而设计的&#xff1b;它采用 key-value 键值对的形式存放元素&#xff08;并封装成 Node 对象&#xff09;&#xff0c;允许使用 null 键和 nul…

笨蛋学设计模式行为型模式-观察者模式【14】

行为型模式-观察者模式 8.1观察者模式:arrow_up::arrow_up::arrow_up:8.1.1概念8.1.2场景8.1.3优势 / 劣势8.1.4观察者模式可分为观察者的基本结构&#xff1a; 8.1.5观察者模式8.1.6实战8.1.6.1题目描述8.1.6.2输入描述8.1.6.3输出描述8.1.6.4代码 8.1.7总结 8.1观察者模式⬆️…

(二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真

一、创建工作空间 mkdir catkin_ws cd catkin_ws mkdir src cd src 二、下载wpr_simulation源码 git clone https://github.com/6-robot/wpr_simulation.git 三、编译 ~/catkin_make 目录下catkin_makesource devel/setup.bash 四、运行 roslaunch wpr_simulation wpb_s…

【Backbone】Vim(Vision Mamba)架构学习笔记

1 学习资料 论文&#xff1a;《Vision Mamba: Efficient Visual Representation Learning with Bidirectional State Space Model》 阅读资料 【博文】《入局CV&#xff0c;Mamba再显神威&#xff01;华科王兴刚团队首次将Mamba引入ViT&#xff0c;更高精度、更快速度、更低…

java小项目:简单的收入明细记事本,超级简单(不涉及数据库,通过字符串来记录)

一、效果 二、代码 2.1 Acount类 package com.demo1;public class Acount {public static void main(String[] args) {String details "收支\t账户金额\t收支金额\t说 明\n"; //通过字符串来记录收入明细int balance 10000;boolean loopFlag true;//控制循…

2023.1.19 关于 Redis 事务详解

目录 Redis 事务对比 MySQL 事务 MySQL 事务 Redis 事务 Redis 事务原子性解释 Redis 事务详解 执行流程 典型使用场景 Redis 事务命令 WATCH 的使用 WATCH 实现原理 总结 阅读下文之前建议点击下方链接了解 MySQL 事务详解 MySQL 事务详解 Redis 事务对比 MySQL 事…

[陇剑杯 2021]jwt

[陇剑杯 2021]jwt 题目做法及思路解析&#xff08;个人分享&#xff09; 问一&#xff1a;昨天&#xff0c;单位流量系统捕获了黑客攻击流量&#xff0c;请您分析流量后进行回答&#xff1a; 该网站使用了______认证方式。&#xff08;如有字母请全部使用小写&#xff09…

C++ 设计模式之备忘录模式

【声明】本题目来源于卡码网&#xff08;题目页面 (kamacoder.com)&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 -- 什么是备忘录模式 &#xff08;第17种模式&#xff09; 备忘录模式&#xff08;Meme…

【C语言】- 设置控制台标题、编码、文字颜色、大小和字体

【C语言】- 设置控制台标题、编码、文字颜色、大小和字体 文章目录 【C语言】- 设置控制台标题、编码、文字颜色、大小和字体1 - 设置控制台标题2 - 设置控制台编码3 - 设置控制台字体和大小参考链接 1 - 设置控制台标题 因为要用到 Windows API&#xff0c;所以需要包含头文件…

apache下的CollectionUtils工具类

目录 依赖 这个包下 1.安全检查指定的集合是否为空 2.空 安全检查指定的集合是否为空 3.(String)类型中的集合-并集 union 4.(String)类型中的集合-交集 5.(String)类型中交集的补集 6.(String)类型中差集&#xff08;扣除&#xff09; 7.containsAny()方法&#xff1…

UI组件在线预览,程序员直呼“不要太方便~”

一、介绍 以往大家如果想查看组件的使用效果&#xff0c;需要打开DevEco Studio构建工程。现在为了便于大家高效开发&#xff0c;文档上线了JS UI组件在线预览功能&#xff0c;无需本地构建工程&#xff0c;在线即可修改组件样式等参数、一键预览编译效果。程序员直呼&#xff…

可视化k8s页面(Kubepi)

Kubepi是一个简单高效的k8s集群图形化管理工具&#xff0c;方便日常管理K8S集群&#xff0c;高效快速的查询日志定位问题的工具 随便在哪个节点部署&#xff0c;我这里在主节点部署 docker pull kubeoperator/kubepi-server docker run --privileged -itd --restartunless-st…

Webpack5入门到原理14:生产模式介绍

生产模式是开发完成代码后&#xff0c;我们需要得到代码将来部署上线。 这个模式下我们主要对代码进行优化&#xff0c;让其运行性能更好。 优化主要从两个角度出发: 优化代码运行性能 优化代码打包速度 生产模式准备 我们分别准备两个配置文件来放不同的配置 1. 文件目录…

求助帖(setiosflags)的左右对齐问题:

以后自己要注意&#xff0c;如果两个相互矛盾的标志同时被设置&#xff0c;如先设置 setiosflags(ios::right)&#xff0c;然后又设置 setiosflags(ios::left)&#xff0c;那么结果可能就是两个标志都不起作用。因此&#xff0c;在设置了某标志&#xff0c;又要设置其他与之矛盾…

RabbitMQ-生产者可靠性

一、生产者重连 1、概念 由于网络波动导致客户端无法连接上MQ&#xff0c;这是可以开启MQ的失败后重连机制。 注意&#xff1a; 是连接失败的重试&#xff0c;而不是消息发送失败后的重试。 2、开启配置 spring:rabbitmq:template:retry:enabled: true # 是否启用重试机制ma…

尝试计算π的值,并统计π的小数部分中每个数字出现的次数,当所有数字出现次数相等时停止计算

from mpmath import mp # 设置π的精度 mp.dps 100000 # 设置所需精度 # 初始化计数器 digit_counts [0] * 10 # 开始尝试计算并统计 for i in range(1, mp.dps 1): # 获取当前精度的π值 pi_str str(mp.pi)[2:] # 跳过"3." # 统计各数字出现次数 …

【RabbitMQ】RabbitMQ安装与使用详解以及Spring集成

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《RabbitMQ实战》。&#x1f3af;&#x1f3af; &am…

java实现红黑树

红黑树 红黑树是一种自平衡二叉查找树&#xff0c;其中每个节点都有一个颜色属性&#xff0c;颜色为红色或黑色。它的特性保证了树在插入和删除操作后仍然保持大致的平衡&#xff0c;使得查找操作能够在对数时间内完成。以下是红黑树的一些基本性质&#xff1a; 每个节点是红…