JAVA:异步任务处理类CompletableFuture让性能提升一倍

一、前言

  CompletableFuture 是 Java 8 引入的一个功能强大的类,用于异步编程。它表示一个可能尚未完成的计算的结果,你可以对其添加回调函数来在计算完成时执行某些操作。在 Spring Boot 应用中,CompletableFuture 可以用于提高应用的响应性和吞吐量。

二、为什么使用异步编程

  场景描述:进入用户个人中心,调用接口。接口需要返回用户基础信息、用户积分、用户等级等,假设用户基础信息查询耗时300ms,查询用户积分、用户等级分别耗时200ms,那么接口返回就需要700+ms了。那么我们如何去优化呢

  如果采用异步(多线程并行)形式,接口将以用户基础信息查询耗时300ms最慢的为主,接口性能提升一倍!查询任务越多,则其性能提升越大!

三、异步编程介绍

  在Java中,Callable、Runnable、Future和CompletableFuture是用于处理并发编程的重要接口和类。它们各自有不同的用途和特性,下面是对它们的简要介绍:

1. Callable
  Callable是一个接口,用于定义返回结果或抛出异常的任务。它类似于Runnable,但Runnable没有返回值且不能抛出受检异常。Callable接口中定义了一个call()方法,该方法可以返回一个值,并且可以抛出一个受检异常。

2. Runnable
  Runnable是一个接口,用于定义没有返回值且不抛出受检异常的任务。它只有一个run()方法,用于执行任务的代码。通常,Runnable对象会被传递给Thread对象的构造函数来创建新线程。

3. Future
  Future是一个接口,用于表示异步计算的结果。它是Java并发包java.util.concurrent的一部分。当你提交一个Callable任务给ExecutorService时,它会返回一个Future对象,该对象表示异步计算的结果。你可以使用Future的get()方法来等待计算完成并获取结果,或者使用isDone()方法来检查计算是否完成。

4. CompletableFuture
  CompletableFuture是Java 8中引入的一个类,实现了Future和CompletionStage接口。它提供了函数式编程的能力来处理异步编程,并允许你链式地组合多个异步操作。CompletableFuture提供了许多方法,如thenApply(), thenAccept(), thenCompose(), exceptionally()等,这些方法允许你定义当异步操作完成时应该执行的操作。

  CompletableFuture相对于Future的优势在于它提供了更丰富的API来处理异步计算的结果,并支持链式调用和组合多个异步操作。这使得编写异步代码更加简洁和直观。

四、CompletableFuture中的函数式编程

  在CompletableFuture类中的方法,很多都是Function函数式接口方法为入参,所以我们先要有一定的认识。

   Supplier<U>  // 生产者,没有入参,有返回结果Consumer<T>  // 消费者,有入参,但是没有返回结果Function<T,U>// 函数,有入参,又有返回结果

五、CompletableFuture核心方法介绍

5.1. 创建异步任务

   supplyAsync: 异步执行一个给定的Supplier函数,并返回一个新的CompletableFuture,其结果由Supplier决定。

  CompletableFuture<Void/Type> supplyAsync(Supplier<U> supplier)CompletableFuture<Void/Type> supplyAsync(Supplier<U> supplier, Executor executor):允许指定执行器。

   runAsync: 异步执行一个Runnable任务,由于没有返回值,所以返回的CompletableFuture类型为Void。

  CompletableFuture<Void> runAsync(Runnable runnable)CompletableFuture<Void> runAsync(Runnable runnable, Executor executor):允许指定执行器。

5.1.1. 代码示例

supplyAsync

CompletableFuture<String>future=CompletableFuture.supplyAsync(()->{System.out.println("compute test");return "test";
});String result = future.join();System.out.println("get result: " + result);

runAsync

CompletableFuture<Void> future = CompletableFuture.runAsync(()->{System.out.println("compute test");
});System.out.println("get result: " + future.join());

指定线程池

ExecutorService executorService = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));CompletableFuture<User> task1 = CompletableFuture.supplyAsync(()->{User userInfo = userService.getUserInfo(1);return userInfo;
}, executorService);// 获取结果
User user = task1.get();

5.2. 流式连接函数介绍

  流式连接函数(也称为组合函数)允许你以函数式编程的方式组合和链接多个异步操作。 假如:我有两个任务,任务2需要等任务1执行完成后,拿到任务1中的结果作为任务2的参数,这时候就需要用到流式连接函数。

①. thenApply 和 thenApplyAsync

  thenApply: 当前CompletableFuture计算完成时,应用给定的函数到其结果上,并返回表示该结果的新的CompletableFuture。

  thenApplyAsync: 与thenApply类似,但异步执行给定的函数。

②. thenAccept 和 thenAcceptAsync

  thenAccept: 当当前CompletableFuture计算完成时,执行给定的动作到其结果上,然后返回void。

  thenAcceptAsync: 与thenAccept类似,但异步执行给定的动作。

③. thenRun 和 thenRunAsync

  thenRun: 当当前CompletableFuture计算完成时,执行给定的动作,不关注其结果。

  thenRunAsync: 与thenRun类似,但异步执行给定的动作。

④. thenCombine 和 thenCombineAsync

  thenCombine: 接收另一个CompletableFuture,并返回一个新的CompletableFuture,该CompletableFuture在当前CompletableFuture和另一个CompletableFuture都完成计算后,使用它们的结果来计算值。

  thenCombineAsync: 与thenCombine类似,但异步执行给定的函数。

⑤. thenCompose 和 thenComposeAsync

  thenCompose: 当当前CompletableFuture计算完成时,应用给定的函数到其结果上,该函数返回一个新的CompletableFuture,并返回表示该新CompletableFuture的结果的CompletableFuture。

  thenComposeAsync: 与thenCompose类似,但异步执行给定的函数。

⑥. whenComplete 和 whenCompleteAsync

  whenComplete: 当当前CompletableFuture正常完成或出现异常时,执行给定的动作。动作有两个参数:结果(如果操作正常完成)或异常(如果抛出异常),以及一个表示该CompletableFuture的Throwable(如果操作抛出异常则为null)。

  whenCompleteAsync: 与whenComplete类似,但异步执行给定的动作。

⑦. handle 和 handleAsync

  handle: 当当前CompletableFuture正常完成或出现异常时,应用给定的函数到其结果或异常上,并返回一个新的CompletableFuture,其结果是函数的结果。

  handleAsync: 与handle类似,但异步执行给定的函数。

  注意带Async后缀的函数表示需要连接的后置任务会被单独提交到线程池中,从而相对前置任务来说是异步运行的。除此之外,两者没有其他区别。

  在使用Async后缀的方法时,你可以提供一个Executor作为可选参数来指定异步操作应在哪个线程上执行。如果未提供,则使用ForkJoinPool.commonPool()。

5.3. 流式连接核心函数代码示例

1. thenApply / thenAccept / thenRun

  这组函数主要用于连接前后有依赖的任务链。这里将thenApply / thenAccept / thenRun放在一起讲,因为这几个连接函数之间的唯一区别是提交的任务类型不一样。

区别如下:

  1.thenApply提交的任务类型需遵从Function签名,也就是有入参和返回值,其中入参为前置任务的结果。

  2.thenAccept提交的任务类型需遵从Consumer签名,也就是有入参但是没有返回值,其中入参为前置任务的结果。

  3.thenRun提交的任务类型需遵从Runnable签名,即没有入参也没有返回值。

代码示例:

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{System.out.println("compute 1");return 1;
});CompletableFuture<Integer> future2 = future1.thenApply((p)->{System.out.println("compute 2");return p+10;
});
System.out.println("result: " + future2.join());

  注意的是,通过thenApply / thenAccept / thenRun连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。

应用场景总结:

  1. thenApply:当需要在异步操作的结果上执行额外的计算或转换时,使用 thenApply。

  2. thenAccept:当只需要消费异步操作的结果,而不关心新的结果或执行额外的任务时,使用 thenAccept。

  3. thenRun:当需要在异步操作完成后执行一个不依赖于其结果的任务时,使用 thenRun。

2. thenCombine

  thenCombine最大的不同是连接任务可以是一个独立的CompletableFuture,从而允许前后连接的两个任务可以并行执行(后置任务不需要等待前置任务执行完成),最后当两个任务均完成时,再将其结果同时传递给下游处理任务,从而得到最终结果

代码示例:
  假设我们有两个异步任务,一个用于获取用户的名字(nameFuture),另一个用于获取用户的年龄(ageFuture)。我们希望当这两个任务都完成后,将它们的结果组合成一个字符串,如 “Name: John, Age: 30”。


CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> getUserName());  
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(() -> getUserAge());  CompletableFuture<String> combinedFuture = nameFuture.thenCombine(ageFuture, (name, age) ->   "Name: " + name + ", Age: " + age  
);  combinedFuture.thenAccept(System.out::println); 
// 输出类似 "Name: John, Age: 30"

  thenAcceptBoth、thenAcceptBothAsync、runAfterBoth、runAfterBothAsync的作用与thenConbime类似,区别如下:

  1. thenAcceptBoth 和 thenAcceptBothAsync 用于处理两个异步操作的结果,但不返回新的结果。

  2. runAfterBoth 和 runAfterBothAsync 也用于处理两个异步操作的完成,但不关注它们的结果。

  3. thenCombine 用于处理两个异步操作的结果,并返回一个新的结果。

3. thenCompose

  thenCompose 的应用场景主要涉及到需要基于一个异步任务的结果来发起另一个异步任务的情况。它允许你将多个异步操作链接在一起,并以前一个操作的结果作为后一个操作的输入。

代码示例:

如果使用thenApply实现如下:CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{System.out.println("compute 1");return 1;
});
CompletableFuture<CompletableFuture<Integer>> future2 =future1.thenApply((r)->CompletableFuture.supplyAsync(()->r+10));
System.out.println(future2.join().join());当连接的任务越多时,代码会变得越来越复杂,嵌套获取层级也越来越深。
使用thenCompose
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{System.out.println("compute 1");return 1;
});
CompletableFuture<Integer> future2 = future1.thenCompose((r)->CompletableFuture.supplyAsync(()->r+10));
System.out.println(future2.join());

4. whenComplete

  whenComplete主要用于注入任务完成时的回调通知逻辑。这个解决了传统future在任务完成时,无法主动发起通知的问题。前置任务会将计算结果或者抛出的异常作为入参传递给回调通知函数。

代码示例:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {  // 模拟从数据库读取数据的异步操作  return "Data from database";  
});  future.whenComplete((result, exception) -> {  if (exception == null) {  // 操作成功,记录成功日志  System.out.println("Operation succeeded. Result: " + result);  } else {  // 操作失败,记录异常日志  System.err.println("Operation failed with exception: " + exception.getMessage());  }  // 在这里还可以执行其他与结果无关的操作,如清理资源、发送通知等  
});

5. handle

  handle与whenComplete的作用有些类似,但是handle接收的处理函数有返回值,而且返回值会影响最终获取的计算结果。

代码示例:

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{System.out.println("compute 1");return 1;
});CompletableFuture<Integer> future2 = future1.handle((r, e)->{if(e != null){System.out.println("compute failed!");return r;} else {System.out.println("received result is " + r);return r + 10;}
});System.out.println("result: " + future2.join());

六、CompletableFuture中获取异步任务结果介绍

异步任务执行完成之后,需要获取结果,有如下一些方法:

  1. allOf 方法用于等待多个 CompletableFuture 任务全部完成。它接受一个 CompletableFuture 数组或列表作为参数,并返回一个新的 CompletableFuture。这个新的 CompletableFuture 会在所有给定的 CompletableFuture 都完成时完成,但它不包含任何原始任务的结果。

  2. anyOf 方法与 allOf 类似,但它只等待给定的 CompletableFuture 中的一个完成。它同样接受一个 CompletableFuture 数组或列表作为参数,并返回一个新的 CompletableFuture。这个新的 CompletableFuture 会在任何一个给定的 CompletableFuture 完成时完成,但它不包含任何原始任务的结果。

  3. join 方法用于等待 CompletableFuture 完成,并返回其结果(如果可用)。如果 CompletableFuture 尚未完成,则 join 会阻塞当前线程,直到它完成。如果 CompletableFuture 异常完成,则 join 会抛出与异常完成相对应的异常。

  4. get 方法与 join 类似,也用于等待 CompletableFuture 完成并返回其结果。但是,get 方法可以接收一个可选的超时参数和一个时间单位,以便在指定的时间内等待 CompletableFuture 完成。如果 CompletableFuture 在指定的时间内没有完成,则 get 方法会抛出 TimeoutException。另外,如果 CompletableFuture 异常完成,则 get 方法会抛出 ExecutionException(包装了原始异常)或 InterruptedException(如果当前线程在等待时被中断)。

  以上就是对CompletableFuture的一些详细介绍,以及一些常用api的代码示例,希望对你在项目进行性能方面的代码优化中有一定的作用。

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

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

相关文章

【NPS】微软NPS配置802.1x,验证域账号,动态分配VLAN(有线网络篇)

上两篇中介绍了如何配置NPS和在WLC上如何配置802.1X来实现验证域账号和动态分配VLAN&#xff0c;802.1x协议作为一种成熟的身份验证框架&#xff0c;不仅适用于无线网络&#xff0c;同样也适用于有线网络环境。这里我们将介绍如何在有线网络中部署802.1x认证&#xff0c;以验证…

Android Coil的简单介绍及使用

前言&#xff1a; 本文是借鉴网上大佬的Coil相关技术文章及结合自己项目中的实际使用情况&#xff0c;对Coil作一个简单介绍。 简介&#xff1a; Coil是一个Android的图片加载框架库&#xff0c;是通过Kotlin协程的方式加载图片的&#xff0c;相对于Glide、Picasso、Fresco等…

SpringBoot启动时使用外置yml文件

第一步&#xff1a;打包时排除yml文件 <build><resources><resource><!-- 排除的文件的路径 --><directory>src/main/resources</directory><excludes><!-- 排除的文件的名称 --><exclude>application-dev.yml</e…

返回值

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 到目前为止&#xff0c;我们创建的函数都只是为我们做一些事&#xff0c;做完了就结束。但实际上&#xff0c;有时还需要对事情的结果进行获取。这类…

使用Redis常遇到的问题

文章目录 概述缓存雪崩、穿透、击穿大key问题热Key问题缓存和数据库双写一致性问题缓存并发竞争Redis线上阻塞要如何排查Redis 常见的性能问题都有哪些Redis 如何做内存优化Redis数据倾斜 概述 在使用Redis时&#xff0c;有几个常见的问题可能会出现&#xff0c;包括但不限于以…

调用上传文件接口出现格式错误

一、造成这种错误的可能有很多 1.检查一下传递格式 2.检查一下接口要求的格式 二、举个例子 这两个有什么区别&#xff1f; 那就是json、和form-data&#xff0c;一定要看仔细接口 如果还是按照json的方式去传就会报错 三、更改header里Content-Type的类型 json等的heade…

AWS CLI 命令行详解

官网 ## aws cli https://docs.aws.amazon.com/zh_cn/cli/latest/userguide/cli-configure-files.html## ec2 api https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instance-status.html 支持的格式 https://docs.aws.amazon.com/zh_cn/cli/v1/userguide/cli…

优雅地设计一个Restful响应体类(结构体)——R

highlight: xcode theme: vuepress 简介 今天给大家介绍一下如何设计一个业务数据响应体类&#xff0c;就是前端每次请求后端返回的数据我们要统一数据结构&#xff0c; 不能想怎么写就怎么写。规定一个通用的后端数据响应体类十分必要。 响应体类R 其实也很简单&#xff0c;只…

strcpy、strncpy、strcat、strncat、strcmp、strstr字符串函数的使用和模拟

strcpy的使用和模拟&#xff08;作用&#xff09; 将一个字符串的内容复制到另外一个字符串中代替掉。 strcpy的使用效果 #include <stdio.h> #include <string.h>int main () {char str1[]"Sample string";char str2[40];char str3[40];strcpy (str2…

Python 学习笔记【1】

此笔记仅适用于有任一编程语言基础&#xff0c;且对面向对象有一定了解者观看 文章目录 数据类型字面量数字类型数据容器字符串列表元组 type()方法数据类型强转 注释单行注释多行注释 输出基本输出连续输出&#xff0c;中间用“,”分隔更复杂的输出格式 变量定义del方法 标识符…

网页实现输入固定前缀,以及打开数量打开固定数量的网页

网页实现输入固定前缀&#xff0c;以及打开数量打开固定数量的网页 废话不多说直接上代码 今天客户说要写一个小需求&#xff1a; 我这边需求是帮我编写一个小程序或者是批处理文件&#xff0c;实现尾数连续的链接打开 例如http://abc1.com,后续依次自动打http://abc2.com,http…

nginx搭建简单负载均衡demo(springboot)

目录 1 安装nignx 1.1 执行 brew install nginx 命令&#xff08;如果没安装brew可百度搜索如何安装brew下载工具。类似linux的yum命令工具&#xff09;。 1.2 安装完成会有如下提示&#xff1a;可以查看nginx的配置文件目录。 1.3 执行 brew services start nginx 命令启动…

9秒爬取庆余年2分集剧情

版本一: 要创建一个Python爬虫程序来爬取指定网站的分集剧情,我们需要使用requests库来发送HTTP请求,以及BeautifulSoup库来解析HTML内容。以下是一个简单的示例,展示了如何爬取你提供的网站的分集剧情,并将每集剧情保存到本地的.txt文件中。 首先,确保你已经安装了req…

if语句知识点

作用 让顺序执行的代码产生分歧。 if 语句 作用&#xff1a;满足条件时&#xff0c;多执行一些代码。 语法&#xff1a; if(bool类型值)//bool类型相关&#xff1a;bool变量&#xff0c;条件运算符表达式&#xff0c;逻辑运算符表达式 {满足条件要执行的代码&#xff0c;写在…

遍历路径,统计每个文件大小

功能&#xff1a;统计&#xff08;参数&#xff09;路径下每个文件大小&#xff0c;记录属主&#xff0c;并且不统计超链接的文件和文件夹。 #!/bin/bash# 定义函数来递归遍历文件夹 function traverse_directory {local directory"$1"# 遍历目录下的文件和子目录fo…

将三个字符串通过strcat连接起来并打印输出

将三个字符串通过strcat连接起来并打印输出 #include <stdio.h> #include <string.h> int main () { char a[10]"I", b[10]" am",c[10]" happy"; strcat(a,b); strcat(a,c); printf("%s",a); printf("\n"); re…

Java | Leetcode Java题解之第123题买卖股票的最佳时机III

题目&#xff1a; 题解&#xff1a; class Solution {public int maxProfit(int[] prices) {int n prices.length;int buy1 -prices[0], sell1 0;int buy2 -prices[0], sell2 0;for (int i 1; i < n; i) {buy1 Math.max(buy1, -prices[i]);sell1 Math.max(sell1, b…

python程序控制结构

文章目录 一、python程序控制结构介绍二、顺序结构2.1、print()函数2.2、end参数2.3、input()函数 三、选择结构3.1选择结构的用途 四、循环结构4.1循环结构的构造4.1.1、循环结构的三个要素4.1.2、循环结构的一个要求4.1.3、循环结构的一个关系 4.2、循环语句4.2.1、while语句…

zynq PS点灯

摸鱼碎碎念&#xff1a; 需要做ADC采集并在TFT屏幕实时显示波形&#xff08;简易示波器&#xff09; 发现只使用fpga实现比较困难 使用的是zynq&#xff0c;刚好来把arm部分也学到 参考视频 与 教材文档 01_Zynq SoC FPGA的诞生_哔哩哔哩_bilibili (这是俺点开AXI4接口协…

爬虫学习2

中国国家地理网 单张图片爬取 import requests url http://img0.dili360.com/ga/M00/02/AB/wKgBzFQ26i2AWujSAA_-xvEYLbU441.jpg!rw9 headers {"User-Agent": Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0…