研发必会-异步编程利器之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、网络安全体系的特征 网络安全体系:网络安全保障系统的最高层概念抽象 特征内容整体性网络安全单元按照一定的规则,相互依赖、相互作用而形成人机物一体化的网络安全保护方式协同性通过各种安全机制的相互协作,构建系统性的网络安全保护方…...
基于Sakura实验板的STM32流水灯项目实战:从GPIO控制到模式切换
1. 项目概述:从零到一,点亮你的第一串“流水”如果你刚拿到一块单片机开发板,面对一堆引脚和代码感到无从下手,那么“流水灯”几乎就是所有嵌入式开发者的“Hello World”。它简单、直观,却能让你快速理解GPIO…...
抖音视频批量下载神器:3分钟学会无水印批量下载技巧
抖音视频批量下载神器:3分钟学会无水印批量下载技巧 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support…...
ZYNQ平台SGMII光口实战:从Vivado连线、设备树到静态IP设置的完整避坑指南
ZYNQ平台SGMII光口实战:从Vivado连线到静态IP部署的全流程解析 在嵌入式系统开发中,以太网通信的稳定实现往往是项目成功的关键。对于采用Xilinx ZYNQ系列FPGA的开发者而言,SGMII(Serial Gigabit Media Independent Interface&…...
读研读博,教你3招搞定文献调研
今天就和大家分享几个我踩坑后总结的高效科研技巧,以及一款能帮你省出大半时间的实用工具——MedPeer的Deep Search。相信每个做科研的人都有过类似的经历:为了找一篇相关文献,翻遍了知网、Web of Science,结果翻了几十页还是找不…...
别再死记硬背ELMo、GPT、BERT的区别了!一张图带你搞懂它们的核心差异与适用场景
一图胜千言:ELMo、GPT、BERT技术差异与实战选型指南 刚接触NLP时,我也曾被各种预训练模型绕得头晕眼花——它们看起来都能处理文本,但面试官一问"为什么用BERT不用GPT"就瞬间语塞。直到我把这些模型拆解成汽车零件,才真…...
终极Windows驱动清理指南:3分钟快速释放C盘隐藏空间
终极Windows驱动清理指南:3分钟快速释放C盘隐藏空间 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 你是否发现Windows系统越用越慢,C盘空间莫名其妙消失&#x…...
别再只用按键了!用STM32F103的ADC读取电位器,给你的无感无刷电机做个“油门”
从油门踏板到电机转速:STM32F103 ADC精准控制无刷电机的交互设计艺术 清晨的咖啡机发出均匀的研磨声,电动滑板车在街道上流畅加速,这些看似简单的机械运动背后,都隐藏着一个精妙的交互设计——如何让人类的手部动作与电机转速建立…...
2026年5月19日:谷歌云误停账户致Railway全平台服务中断8小时
事件报告:2026年5月19日 - GCP账户暂停Chandrika Khanduri 与 Cody De Arkland于2026年5月20日发布此报告。据悉,本报告反映了发布时所掌握的信息,可能会根据谷歌云(Google Cloud)的内部审查结果进行更新。影响2026年5…...
FPGA数学库设计:从定点数、CORDIC到AXI-Stream的硬件算法实现
1. 项目概述:为什么我们需要一个FPGA数学库?如果你在FPGA开发中做过信号处理、图像算法或者任何需要复杂数学运算的设计,大概率会面临一个共同的困境:如何高效、可靠地实现那些看似基础的数学函数?比如,计算…...
2026年六大主流AI变声器软件排名推荐!
随着AI语音技术持续迭代升级,AI变声器不再是单一的娱乐工具,广泛应用于游戏开黑、直播互动、短视频配音、音频创作、隐私语音沟通等多个场景。目前市面上变声软件品类繁杂,涵盖移动端、PC端、免费开源、专业付费等不同类型,普通用…...
