研发必会-异步编程利器之CompletableFuture(含源码 中)
近期热推文章:
1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;
2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;
3、基于Redis的Geo实现附近商铺搜索(含源码)
4、基于Redis实现关注、取关、共同关注及消息推送(含源码)
5、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)
6、基于Redis实现点赞及排行榜功能
一、多任务组合回调

备注:源码获取方式在文底。
1.1、AND组合关系

thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务。也即:当任务一和任务二都完成再执行任务三(异步任务)。
区别在于:
1、runAfterBoth:不会把执行结果当做方法入参,且没有返回值。
2、thenAcceptBoth:会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值。
3、thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值。
代码案例:
/*** 功能描述:多任务组合回调:AND组合关系* @MethodName: testCompleteAnd* @MethodParam: []* @Return: void* @Author: yyalin* @CreateDate: 2023/10/11 17:30*/public void testCompleteAnd() throws ExecutionException, InterruptedException {//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);long startTime = System.currentTimeMillis();//1、使用自定义线程池,开启异步任务01CompletableFuture<Integer> supplyAsyncRes01=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务1 开始执行任务01,当前线程为:12log.info("开始执行任务01,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=1; //模拟加1} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//2、使用自定义线程池,开启异步任务02CompletableFuture<Integer> supplyAsyncRes02=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务02 开始执行任务02,当前线程为:13log.info("开始执行任务02,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=2; //模拟加2} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;});//3、任务02:将任务1与任务2开始任务组合CompletableFuture<Integer> thenCombineAsyncRes=supplyAsyncRes01.thenCombineAsync(supplyAsyncRes02,(res01, res02)->{//始执行任务03,当前线程为:14log.info("开始执行任务03,当前线程为:"+Thread.currentThread().getId());log.info("任务01返回值:"+res01);log.info("任务02返回值:"+res02);//任务组合返回值 可以拿到任务01和任务02的返回结果进行相关操作,然后统一返回结果return res01+res02;},executorService);//4、最终返回结果log.info("最终返回结果为:"+thenCombineAsyncRes.get());log.info("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}
运行结果:

1.2、OR组合关系

将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。(两个任务,只要有一个任务完成,就执行任务三)
区别在于:
1、runAfterEither:不会把执行结果当做方法入参,且没有返回值。
2、acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值。
3、applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值。(个人推荐)
参考代码:
/*** 功能描述:OR组合关系* @MethodName: testCompleteOr* @MethodParam: []* @Return: void* @Author: yyalin* @CreateDate: 2023/10/11 18:14*/public void testCompleteOr(){//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);long startTime = System.currentTimeMillis();//1、使用自定义线程池,开启异步任务01CompletableFuture<Integer> supplyAsyncRes01=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务1 开始执行任务01,当前线程为:12log.info("开始执行任务01,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=2; //模拟加1} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//2、使用自定义线程池,开启异步任务02CompletableFuture<Integer> supplyAsyncRes02=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务02 开始执行任务02,当前线程为:13log.info("开始执行任务02,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=3; //模拟加2} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//3、任务组合orsupplyAsyncRes01.acceptEitherAsync(supplyAsyncRes02,(res)->{try {log.info("开始执行任务03,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);log.info("上一个任务返回值:"+res);log.info("总共用时" + (System.currentTimeMillis() - startTime) + "ms");} catch (InterruptedException e) {e.printStackTrace();}},executorService);}
返回结果:

若将异步任务02中的Thread.sleep(600)改为300,将输出的结果为:

从结果中不难对比发现,任务03的参数是任务01和任务02中执行最快的返回结果。
注意:若把核心线程数量改为1,会是什么样的呢?
ExecutorService executorService = Executors.newFixedThreadPool(1);
运行结果:

从上面看出,改为1就变成单线程执行了。
1.3、多任务组合(allOf\anyOf)

1.allOf:等待所有任务都执行完成后,才会执行 allOf 返回的CompletableFuture。如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常。(等待所有任务完成才会执行)
2.anyOf:任意一个任务执行完,就执行anyOf返回的CompletableFuture。如果执行的任务异常,anyOf的CompletableFuture,执行get方法,会抛出异常。(只要有一个任务完成)
参考案例:
public void testAllOfOrAnyOf() throws ExecutionException, InterruptedException {//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);long startTime = System.currentTimeMillis();//1、使用自定义线程池,开启异步任务01CompletableFuture<Integer> supplyAsyncRes01=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务1 开始执行任务01,当前线程为:12log.info("开始执行任务01,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=3; //模拟加1} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//2、使用自定义线程池,开启异步任务02CompletableFuture<Integer> supplyAsyncRes02=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务02 开始执行任务02,当前线程为:13log.info("开始执行任务02,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=4; //模拟加2} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//3、使用自定义线程池,开启异步任务03CompletableFuture<Integer> supplyAsyncRes03=CompletableFuture.supplyAsync(()->{int res=1;try {//执行任务02 开始执行任务02,当前线程为:13log.info("开始执行任务03,当前线程为:"+Thread.currentThread().getId());//执行具体的事务Thread.sleep(600);res+=5; //模拟加2} catch (InterruptedException e) {e.printStackTrace();}//返回结果return res;},executorService);//4、开始任务组合CompletableFuture<Void> allOfRes=CompletableFuture.allOf(supplyAsyncRes01,supplyAsyncRes02,supplyAsyncRes03);//等待所有任务完成log.info("所有任务执行完成,组合后返回结果为:"+allOfRes.get());//获取所有任务的返回结果log.info("任务01返回值:"+supplyAsyncRes01.get());log.info("任务02返回值:"+supplyAsyncRes02.get());log.info("任务03返回值:"+supplyAsyncRes03.get());log.info("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}
结果返回:

从结果中看出:等待所有任务都执行完成后,才会执行 allOf 返回的CompletableFuture。
同理anyOf,只需要调整代码:
CompletableFuture<Object> allOfRes=CompletableFuture.anyOf(supplyAsyncRes01,supplyAsyncRes02,supplyAsyncRes03);
运行结果:

1.4、thenCompose
thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例。
1、如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
2、如果该CompletableFuture实例为null,然后就执行这个新任务。
代码案例:
/*** 功能描述:thenCompose* @MethodName: testThenCompose* @MethodParam: []* @Return: void* @Author: yyalin* @CreateDate: 2023/10/12 9:38*/public void testThenCompose() throws ExecutionException, InterruptedException {CompletableFuture<String> res01=CompletableFuture.completedFuture("任务01");ExecutorService executor = Executors.newSingleThreadExecutor();//第二个任务 在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法,// 该方法会返回一个新的CompletableFuture实例。CompletableFuture<String> futureRes =CompletableFuture.supplyAsync(()-> "第二个任务02",executor).thenComposeAsync(data->{log.info("data数据为:"+data);return res01;},executor);log.info("最终返回:"+futureRes.get());executor.shutdown();}
结果:

二、使用注意点
CompletableFuture 使异步编程更加便利的、代码更加优雅的同时,也要关注使用的一些注意点。

2.1、Future需要获取返回值,才能获取异常信息
代码案例:
/*** 功能描述:使用注意点* @MethodName: testFuture* @MethodParam: []* @Return: void* @Author: yyalin* @CreateDate: 2023/10/12 9:54*/public void testFuture() throws ExecutionException, InterruptedException {//自定义线程池ExecutorService executorService = new ThreadPoolExecutor(5,10,5L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));//创建任务CompletableFuture<Void> res01=CompletableFuture.supplyAsync(()->{int sum=1/0;return "分母不能为0";},executorService).thenAccept((res)->{ //3、异常捕获log.info("系统出现异常,需要处理:"+res);});log.info("返回结果:"+res01.get());}
输出结果:

Future需要获取返回值(res01.get()),才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。使用的时候,注意一下,考虑是否加try…catch…或者使用exceptionally方法。
若改成exceptionally方法,无需get或join也可以捕获异常信息:
CompletableFuture<String> res01=CompletableFuture.supplyAsync(()->{int sum=1/0;return "分母不能为0";},executorService).exceptionally((throwable)->{ //3、异常捕获log.info("系统出现异常,需要处理:"+throwable.getMessage());return "00";});// log.info("返回结果:"+res01.get());
结果:
![]()
2.2、CompletableFuture的get()方法是阻塞的
CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。

推荐使用:
log.info("返回结果:"+res01.get(5,TimeUnit.SECONDS));
2.3、建议使用自定义线程池,不要使用默认的
CompletableFuture代码中使用了默认的线程池,处理的线程个数是电脑CPU核数-1。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。
参考案例:
//自定义线程池ExecutorService executorService = new ThreadPoolExecutor(5,10,5L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离。
/*** 参数信息:* int corePoolSize 核心线程大小* int maximumPoolSize 线程池最大容量大小* long keepAliveTime 线程空闲时,线程存活的时间* TimeUnit unit 时间单位* BlockingQueue<Runnable> workQueue 任务队列。一个阻塞队列* AbortPolicy(默认):直接抛弃*/ThreadPoolExecutor pool = new ThreadPoolExecutor(4,4,0L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(10),new ThreadPoolExecutor.AbortPolicy());
说明:
AbortPolicy(默认):直接抛弃
CallerRunsPolicy:用调用者的线程执行任务
DiscardOldestPolicy:抛弃队列中最久的任务
DiscardPolicy:抛弃当前任务。
三、源码获取方式
更多优秀文章,请关注个人微信公众号或搜索“程序猿小杨”查阅。然后回复:源码,可以获取对应的源码,开箱即可使用。


如果大家对相关文章感兴趣,可以关注微信公众号"程序猿小杨",会持续更新优秀文章!欢迎大家 分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!

参考网站:
https://blog.csdn.net/ThinkWon/article/details/123390393
https://mp.weixin.qq.com/s/shjANruBk6VL492JaWLTEg
相关文章:
研发必会-异步编程利器之CompletableFuture(含源码 中)
近期热推文章: 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis的Geo实现附近商铺搜索(含源码) 4、基于Redis实现关注、取关、共同关注及消息推送(含源码) 5…...
上海亚商投顾:沪指高开高走 锂电等新能源赛道大反攻
上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指昨日高开后强势震荡,创业板指盘中一度翻绿,随后探底回升再度走高。碳酸锂期货合约…...
力扣第235题 二又搜索树的最近公共祖先 c++
题目 235. 二叉搜索树的最近公共祖先 中等 (简单) 相关标签 树 深度优先搜索 二叉搜索树 二叉树 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q&…...
时代风口中的Web3.0基建平台,重新定义Web3.0!
近年来,Web3.0概念的广泛兴起,给加密行业带来了崭新的叙事方式,同时也为加密行业提供了更加具有想象力的应用场景与商业空间,并让越来越多的行业从业者们意识到只有更大众化的市场共性需求才能推动加密市场的持续繁荣。当前围绕这…...
React学习笔记 001
什么是React 1.发送请求获取数据 处理数据(过滤、整理格式等) 3.操作DOM呈现页面 react 主要是负责第三部 操作dom 处理页面 数据渲染为HTML视图的开源js库。 好处 避免dom繁琐 组件化 提升复用率 特点 声明式编程: 简单 组件化编程…...
2023 | github无法访问或速度慢的问题解决方案
github无法访问或速度慢的问题解决方案 前言: 最近经常遇到github无法访问, 或者访问特别慢的问题, 在搜索了一圈解决方案后, 有些不再有效了, 但是其中有几个还特别好用, 总结一下. 首选方案 直接在github.com的域名上加一个fast > githubfast.com, 访问的是与github完全相…...
unity各种插件集合(自用)
2D Animation——2D序列帧/骨骼动画相关 2D PSD Importer——psb骨骼动画(unity官方建议使用psb而非psd) (Advanced —show preview package 勾选)出现 2D IK——反向动力学IK Universal RP——升级项目到Urp(通用渲…...
内网收集哈希传递
1.内网收集的前提 获得一个主机权限 补丁提权 可以使用 systeminfo 然后使用python脚本找到缺少的补丁 下载下来 让后使用exp提权 收集信息 路由信息 补丁接口 dns域看一看是不是域控 扫描别的端口 看看有没有内在的web网站 哈希传递 哈希是啥 哈希…...
前端目录笔记
HTML HTML 笔记:初识 HTML(HTML文本标签、文本列表、嵌入图片、背景色、网页链接)-CSDN博客html 笔记:CSS_UQI-LIUWJ的博客-CSDN博客HTML 笔记 表格_UQI-LIUWJ的博客-CSDN博客 javascript JavaScript 笔记 初识JavaScript&…...
Sui主网升级至V1.11.2版本
Sui主网现已升级至V1.11.2版本,同时Sui协议升级至27版本。其他升级要点如下: 对于一些更高级别的交易,更改了一些gas费设置,使其gas费消耗的更快。这些更改不影响以前在网络上运行的任何交易,只是为了确保在开始大量使…...
Mysql-数据库和数据表的基本操作
Mysql数据库和数据表的基本操作 一.数据库 1.创建数据库 创建数据库就是在数据库系统中划分一块空间存储数据 (1)语法 create database 数据库名称;(2)查看数据库 show create database 数据库名;(3)…...
拓扑排序求最长路
P1807 最长路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目要求我们求出第1号到第n号节点之间最长的距离。 我们想到使用拓扑排序来求最长路。 正常来讲,我们应该把1号节点入队列,再出队列,把一号节点能到达的所有的点的入度减一&a…...
sqli-lab靶场通关
文章目录 less-1less-2less-3less-4less-5less-6less-7less-8less-9less-10 less-1 1、提示输入参数id,且值为数字; 2、判断是否存在注入点 id1报错,说明存在 SQL注入漏洞。 3、判断字符型还是数字型 id1 and 11 --id1 and 12 --id1&quo…...
使用 Apache Camel 和 Quarkus 的微服务(五)
【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 在本系列的第三部分中,我们了解了如何在 Minikube 中部署基于 Quarkus/Camel 的微服务,这是最常用的 Kubernetes 本地实现之一。虽然这样的本地…...
Ubuntu磁盘满了,导致黑屏
前言 (1)最近要玩Milk-V Duo,配置环境过程中,发现磁盘小了。于是退出虚拟机,扩大Ubuntu大小,重新开机,发现无法进入Ubuntu界面。 (2)查了很久,后面发现是磁盘…...
安装sklearn包错误解决以及 scikit-learn简介
安装sklearn包错误解决以及 scikit-learn简介 利用 pip install sklearn时出现错误 pip install sklearn Looking in indexes: https://mirrors.aliyun.com/pypi/simple/ Collecting sklearnUsing cached https://mirrors.aliyun.com/pypi/packages/b9/0e/b2a4cfaa9e12b9ca4…...
CSS点击切换或隐藏盒子的卷起、展开效果
<template><div class"main"><el-button click"onCllick">切换</el-button><transition name"slideDown"><div class"info" v-if"isShow">1111</div></transition></di…...
关于信息安全软考的一些记录1
1、网络信息安全的基本属性 机密性:网络信息不泄露给非授权的用户完整性:未经授权必能改的特性可用性:可以及时获取网络信息和服务的特性可控性:责任主体对网络信息系统具有管理、支配的能力【可管理、可支配】扛抵赖性ÿ…...
如何选择UMLChina服务
服务口号:聚焦最后一公里 斐力庇第斯从马拉松跑回雅典报信,虽然已是满身血迹、精疲力尽,但他知道:没有出现在雅典人民面前,前面的路程都是白费。 学到的知识如果不能最终【用】于您自己的项目之中,也同样是…...
关于信息安全软考的记录3
1、网络安全体系的特征 网络安全体系:网络安全保障系统的最高层概念抽象 特征内容整体性网络安全单元按照一定的规则,相互依赖、相互作用而形成人机物一体化的网络安全保护方式协同性通过各种安全机制的相互协作,构建系统性的网络安全保护方…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
