CompletableFuture 异步编排、案例及应用小案例

本文通过一个掘金文章页面数据查询的应用场景,深入探讨Java CompletableFuture的使用,包括创建异步任务、thenRun/thenApplyAsync、thenAccept、exceptionally/handle等方法的实践,以及allof和anyof的合并任务处理。通过实例解析,阐述CompletableFuture如何提升性能,优化异步编程体验。

前言

今天的话,就来以一个应用场景来进行一步一步的推导,在实现案例的过程中,将CompletableFuture相关的知识点逐步讲述明白。

应用场景如下

我们在查询掘金文章页面数据为应用场景,如何使用异步编程进行优化。

以掘金展示页面为例,点进文章页面时,大致需要渲染的数据为以下几点:

 //1. 文章内容信息 1s
 //2. 作者相关信息 0.5s 依赖于1的查询结果
 //3. 文章评论信息 0.5s 依赖于1的查询结果
 //4. 文章分类信息 0.5s 依赖于1的查询结果
 //5. 文章专栏信息 0.5s 依赖于1的查询结果
 //6. 相关文章信息 0.5s 依赖于1的查询结果
 //...
复制代码

补充:这是我随意拆分的,里面具体的表结构和接口请求先后的关系,以及具体的请求时间都是比较随意的,具体想要陈述的就是同步和异步编程的关系。

那么我们就要根据这个组装一个视图数据来进行返回。

注意:实际上并非是如此,掘金文章页面内容的数据是多个接口返回的,我只是为了模拟内容,这么写罢了,切勿当真,真正应用还需要分业务场景,或者应用场景中可异步编排,到那个时候希望大家能应用上。


现在来说:按照以前我们以前串行化的执行方式,那么总花费的时间就是3.5s,也就是从一开始执行到六,无疑这样是非常慢的,并且 2,3,4,5,6都是依赖于 1的结果查询,但2,3,4,5,6并不互相依赖,此时我们可以将他们从串行化变成异步执行,自己准备一个线程池,然后在执行的时候,将它们放进线程池中异步运行。

如此总耗费时间就从原来的 3.5s 变成了 1.5s,编程思想的改变,对于性能还是有一定程度的提高的

接下来我们就开始接触CompletableFuture

一、CompletableFuture 引入之前

再讲CompletableFuture之前,我还是秉承着一贯的理念,先讲述一些之前的东西,然后再将它引入进来,不至于让大家对于它的出现处于一种非常迷茫的状态。


在之前我们如果只是普通异步的执行一个任务,无需返回结果的话,只要将一个任务实现 Runnable接口,然后将放进线程池即可。

如果需要返回结果,就让任务实现Callable接口,但实际上Callable与 Thread 并没有任何关系,Callable 还需要使用Future与线程建立关系,然后再让线程池执行,最后通过futureTask.get()方法来获取执行的返回结果。

futureTaskFuture接口的一个基本实现。

(Callable 类似于Runnable 接口,但 Runnable 接口中的 run()方法不会返回结果,并且也无法抛出经过检查的异常,但是 Callable中 call()方法能够返回计算结果,并且也能够抛出经过检查的异常。)

一个小案例:

 /**
  * @description:
  * @author: Yihui Wang
  * @date: 2022年08月21日 11:34
  */
 public class Demo {
     public static ExecutorService executorService = new ThreadPoolExecutor(
             10,
             100,
             30L,
             TimeUnit.SECONDS,
             new LinkedBlockingQueue<Runnable>(100),
             Executors.defaultThreadFactory(),
             new ThreadPoolExecutor.DiscardOldestPolicy());
 ​
     public static void runnableTest(){
         RunnableTest runnableTest = new RunnableTest();
         executorService.submit(runnableTest);
     }
 ​
     public static void callableTest() throws ExecutionException, InterruptedException, TimeoutException {
         CallableAndFutureTest callableAndFutureTest = new CallableAndFutureTest();
         FutureTask<String> task = new FutureTask<>(callableAndFutureTest);
         // 采用线程池执行完程序并不会结束, 如果是想测试一次性的那种 可以采用
         // new Thread(task).start();
         executorService.submit(task);
         //System.out.println("尝试取消任务,传true表示取消任务,false则不取消任务::"+task.cancel(true));
         System.out.println("判断任务是否已经完成::"+task.isDone());
         //结果已经计算出来,则立马取出来,如若摸没有计算出来,则一直等待,直到结果出来,或任务取消或发生异常。
         System.out.println("阻塞式获取结果::"+task.get());
         System.out.println("在获取结果时,给定一个等待时间,如果超过等待时间还未获取到结果,则会主动抛出超时异常::"+task.get(2, TimeUnit.SECONDS));
     }
     public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
         runnableTest();
         callableTest();
     }
 ​
 }
 ​
 class RunnableTest implements Runnable{
     @Override
     public void run() {
         System.out.println("我是Runnable执行的结果,我无法返回结果");
     }
 }
 class CallableAndFutureTest implements Callable<String> {
     @Override
     public String call() throws Exception {
         String str = "";
         for (int i = 0; i < 10; i++) {
             str += String.valueOf(i);
             Thread.sleep(100);
         }
         return str;
     }
 }
 ​
复制代码

看起来Callable搭配Future好像已经可以实现我们今天要实现的效果了,从结果的意义上来说,确实可以,但是并不优雅,也会存在一些问题。

如果多个线程之间存在依赖组合,该如何呢?

这个时候就轮到 CompletableFuture出现了~

二、CompletableFuture 案例

我先直接将实现应用场景的效果代码写出来,然后再接着慢慢的去讲

 package com.nzc;
 ​
 import lombok.Data;
 ​
 import java.util.concurrent.*;
 ​
 /**
  * @description:
  * @author: Yihui Wang
  * @date: 2022年08月21日 11:48
  */
 public class CompletableFutureDemo {
 ​
     public static ExecutorService executorService = new ThreadPoolExecutor(
             10,
             100,
             30L,
             TimeUnit.SECONDS,
             new LinkedBlockingQueue<Runnable>(100),
             Executors.defaultThreadFactory(),
             new ThreadPoolExecutor.DiscardOldestPolicy());
 ​
     public static ArticleVO asyncReturn(){
         ArticleVO article=new ArticleVO();
         long startTime=System.currentTimeMillis();
         CompletableFuture<ArticleVO> articleContent = CompletableFuture.supplyAsync(() -> {
             try {
                 article.setId(1L);
                 article.setContent("我是宁在春写的文章内容");
                 Thread.sleep(1000);
             } catch (Exception e) {
                 e.printStackTrace();
             }
             return article;
         },executorService);
 ​
         // 这里的res 就是第一个个 CompletableFuture 执行完返回的结果
         // 如果要测试它们的异步性,其实应该在下面的所有执行中,都让它们沉睡一会,这样效果会更加明显
         // executorService 是放到我们自己创建的线程池中运行
         CompletableFuture<Void> author = articleContent.thenAcceptAsync((res) -> {
             res.setAuthor(res.getId()+"的作者是宁在春");
         },executorService);
 ​
         CompletableFuture<Void> articleComment = articleContent.thenAcceptAsync((res) -> {
             res.setComment(res.getId()+"的相关评论");
         },executorService);
 ​
         CompletableFuture<Void> articleCategory = articleContent.thenAcceptAsync((res) -> {
             res.setCategory(res.getId()+"的分类信息");
         },executorService);
 ​
         CompletableFuture<Void> articleColumn = articleContent.thenAcceptAsync((res) -> {
             res.setColumn(res.getId()+"的文章专栏信息");
         },executorService);
 ​
         CompletableFuture<Void> recommend = articleContent.thenAcceptAsync((res) -> {
             res.setRecommend(res.getId()+"的文章推荐信息");
         },executorService);
 ​
         CompletableFuture<Void> futureAll = CompletableFuture.allOf(articleContent, author, articleComment, articleCategory, articleColumn, recommend);
 ​
         try {
             // get() 是一个阻塞式方法 这里是阻塞式等待所有结果返回
             // 因为要等待所有结果返回,才允许方法的结束,否则一些还在执行中,但是方法已经返回,就会造成一些错误。
             futureAll.get();
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
         long endTime=System.currentTimeMillis();
         System.out.println("耗费的总时间===>"+(endTime-startTime));
 ​
         // 所有任务执行完成后,将构建出来的视图结果进
### 使用 `CompletableFuture` 进行异步操作的编排 #### 什么是 `CompletableFuture` `CompletableFuture` 是 Java 8 中引入的一个强大工具,用于简化异步编程的复杂性[^1]。它不仅继承了 `Future` 接口的功能,还增加了许多新的特性,比如支持函数式编程风格、链式调用以及多种组合方法。 #### 创建和使用 `CompletableFuture` 以下是几个常见的场景及其对应的代码示例: --- ##### 场景一:简单的异步任务 如果有一个独立的任务需要异步执行,则可以直接使用 `runAsync()` 或者 `supplyAsync()` 方法来启动该任务。 ```java // 异步运行无返回值的任务 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { System.out.println("Running async task..."); }); future.join(); // 等待任务完成 ``` 对于有返回值的情况,可以使用 `supplyAsync()`: ```java // 异步运行并返回结果的任务 CompletableFuture<String> resultFuture = CompletableFuture.supplyAsync(() -> { return "Hello from supplyAsync"; }); String result = resultFuture.join(); System.out.println(result); ``` --- ##### 场景二:任务链式调用 当一个任务的结果作为另一个任务的输入时,可以使用 `.thenApply()` 来构建任务链条。 ```java CompletableFuture<Integer> chainResult = CompletableFuture.supplyAsync(() -> { return 42; }).thenApply(number -> number * 2); int finalResult = chainResult.join(); System.out.println(finalResult); // 输出 84 ``` 还可以进一步扩展此链条,例如加入日志记录或其他逻辑: ```java CompletableFuture<Integer> extendedChain = CompletableFuture.supplyAsync(() -> { return 42; }) .thenApply(number -> number * 2) .thenApply(number -> number + 10) // 添加额外的操作 .handle((result, exception) -> { // 处理异常或正常结果 if (exception != null) { return -1; // 返回默认值 } return result; }); System.out.println(extendedChain.join()); // 输出最终结果 ``` --- ##### 场景三:并发任务组合 有时我们需要等待多个异步任务全部完成后再继续下一步工作,这时可以用 `allOf()`;而如果我们只关心第一个完成的任务,则可以选择 `anyOf()`。 ###### 使用 `allOf()` 假设存在两个耗时较长的任务 A 和 B,只有它们都完成后才能得到最终结果: ```java CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) {} return "Task A Result"; }); CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) {} return "Task B Result"; }); // 组合两个任务 CompletableFuture<Void> combinedTasks = CompletableFuture.allOf(taskA, taskB); combinedTasks.join(); System.out.println("All tasks completed."); System.out.println("Task A: " + taskA.join()); System.out.println("Task B: " + taskB.join()); ``` ###### 使用 `anyOf()` 假如只需其中一个任务先完成即可提前结束流程: ```java CompletableFuture<?> firstCompleted = CompletableFuture.anyOf( CompletableFuture.supplyAsync(() -> { try { Thread.sleep(500); } catch (InterruptedException e) {} return "Fast Task"; }), CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) {} return "Slow Task"; }) ); System.out.println(firstCompleted.join()); // 打印较快的那个任务结果 ``` --- ##### 场景四:错误处理 在实际开发过程中不可避免会遇到各种异常情况,因此合理地捕获和处理这些异常非常重要。可以通过 `.handle()` 或 `.exceptionally()` 实现这一点。 ```java CompletableFuture<String> errorHandlingExample = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Something went wrong!"); }).exceptionally(ex -> { return "Default Value"; // 当发生异常时返回备用数据 }); System.out.println(errorHandlingExample.join()); // 输出 Default Value ``` 或者更复杂的例子结合 `.whenComplete()` 提供更多上下文信息: ```java CompletableFuture<String> complexErrorHandling = CompletableFuture.supplyAsync(() -> { throw new IllegalArgumentException("Invalid input"); }).whenComplete((value, throwable) -> { if (throwable != null) { System.err.println("Exception occurred: " + throwable.getMessage()); } else { System.out.println("Value computed successfully: " + value); } }); complexErrorHandling.join(); ``` --- #### 总结 通过上述案例可以看出,`CompletableFuture` 不仅能帮助开发者轻松管理单个异步任务,还能高效协调多任务之间的关系,并且具备完善的错误恢复机制[^3]。相比传统的 `Future` API 更加灵活便捷[^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值