CompletableFuture5-多阶段异步任务执行(多个异步任务相互依赖)

一.多阶段异步任务

在我们的实际生产过程中,可能会遇到这种场景:
   我们需要有多个异步任务,并且这些异步任务是相互依赖的,比如说,现在有三个异步任务,任务2需要使用到任务1的计算结果,任务3又需要用到任务2的计算结果,那应该如何去处理呢?使用CompletableFuture就非常简单了.

二.代码演示

1.thenApply

1.该方法可以将异步任务串行化
2.该方法对于异常的处理是:
当某一步出现异常时,直接执行异常处理和回调函数,不进行后面异步任务的处理

举例:
当没有异常时:

 ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            System.out.println("第一个异步任务执行完毕...");
            return 1;
        },executorService).thenApply(result -> {
            System.out.println("第二个异步任务执行完毕...");
            return result+1;
        }).thenApply(result -> {
            System.out.println("第三个异步任务执行完毕...");
            return result+1;
        }).whenComplete((result,e) -> {
            System.out.println("异步任务执行完毕,已执行回调函数.最终得结果为:"+result);
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println(e.getCause());
            return null;
        });
        executorService.shutdown();

输出结果:
在这里插入图片描述
当出现异常时:

ExecutorService executorService = Executors.newFixedThreadPool(3);
            CompletableFuture.supplyAsync(() -> {
                System.out.println("第一个异步任务执行完毕...");
                return 1;
            },executorService).thenApply(result -> {
                //制造异常
                int i=2/0;
                System.out.println("第二个异步任务执行完毕...");
                return result+1;
            }).thenApply(result -> {
                System.out.println("第三个异步任务执行完毕...");
                return result+1;
            }).whenComplete((result,e) -> {
                System.out.println("异步任务执行完毕,已执行回调函数.最终得结果为:"+result);
            }).exceptionally(e -> {
                System.out.println("程序出现异常...");
                System.out.println(e.getCause());
                e.printStackTrace();
                return null;
            });
            executorService.shutdown();

输出结果:
在这里插入图片描述

2.handle

该方法和thenApply差不多,区别是对异常得处理
当出现异常时,可以根据所带得异常参数进行处理,后面得异步任务也可以接着执行

举例:
无异常得情况和thenApply是一样得,这里只举例有异常得情况

 ExecutorService executorService = Executors.newFixedThreadPool(3);
            CompletableFuture.supplyAsync(() -> {
                System.out.println("第一个异步任务执行完毕...");
                return 1;
            },executorService).handle((result,e) -> {
                //制造异常
                int i=2/0;
                System.out.println("第二个异步任务执行完毕...");
                return result+1;
            }).handle((result,e) -> {
                System.out.println("第三个异步任务执行完毕...");
                return result+1;
            }).whenComplete((result,e) -> {
                System.out.println("异步任务执行完毕,已执行回调函数.最终得结果为:"+result);
            }).exceptionally(e -> {
                System.out.println("程序出现异常...");
                System.out.println(e.getCause());
                e.printStackTrace();
                return null;
            });
            executorService.shutdown();

输出结果:
在这里插入图片描述
可以看到这里虽然第二个异常任务出现异常没有执行完毕,但仍然会执行第三个异步任务.

3.thenAccept.

thenAccept和thenApply也差不多,会新起一个异步任务,并且依赖前一个异步任务得结果,区别是:
thenAccept方法不需要返回值
并且因为thenAccept方法没有返回值,在使用thenAccept方法后调用回调函数时,返回得结果是null.

举例:

 ExecutorService executorService = Executors.newFixedThreadPool(3);
            CompletableFuture.supplyAsync(() -> {
                System.out.println("第一个异步任务执行完毕...");
                System.out.println("===========================================");
                return 1;
            },executorService).thenAccept((result) -> {
                System.out.println("第二个异步任务开始执行...");
                System.out.println("接收到第一个异步任务得结果:"+result);
                System.out.println("第二个异步任务执行完毕...");
                System.out.println("===========================================");
            }).whenComplete((result,e) -> {
                System.out.println("异步任务执行完毕,已执行回调函数.最终得结果为:"+result);
            }).exceptionally(e -> {
                System.out.println("程序出现异常...");
                System.out.println(e.getCause());
                e.printStackTrace();
                return null;
            });
            executorService.shutdown();

输出结果:
在这里插入图片描述

三.thenApply、handle等和对应Async方法得区别

之前说明了thenApply,handle,thenAccept方法得用法和区别,那其实这几个方法都有对应得Async方法.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那主要得区别是什么呢?其实主要是使用线程池得区别,这里以thenApply方法进行举例说明.其他两个和这个是一样得.

1.当没有指定线程池时

thenApply和thenApplyAsync是一样得,都是使用得默认得ForkJoinPool线程池.

 CompletableFuture.supplyAsync(() -> {
                System.out.println("第一个异步任务执行完毕...使用得线程为:"+Thread.currentThread().getName());
                System.out.println("===========================================");
                return 1;
            }).thenApply((result) -> {
                System.out.println("第二个异步任务执行完毕...");
                System.out.println("使用得线程为:"+Thread.currentThread().getName());
                System.out.println("使用方法为:thenApply");
                System.out.println("===========================================");
                return result+1;
            }).thenApplyAsync(result -> {
                System.out.println("第三个异步任务执行完毕...");
                System.out.println("使用得线程为:"+Thread.currentThread().getName());
                System.out.println("使用方法为:thenApplyAsync");
                System.out.println("===========================================");
                return result+1;
            }).whenComplete((result,e) -> {
                System.out.println("所有异步任务执行完毕.");
            }).exceptionally(e -> {
                System.out.println("程序出现异常...");
                System.out.println(e.getCause());
                e.printStackTrace();
                return null;
            });

输出结果:
在这里插入图片描述

2.当使用指定线程池时

thenApply会使用指定线程池,而thenApplyAsync会使用ForkJoinPool线程池.

       ExecutorService executorService = Executors.newFixedThreadPool(3);

        CompletableFuture.supplyAsync(() -> {
            System.out.println("第一个异步任务执行完毕...使用得线程为:"+Thread.currentThread().getName());
            System.out.println("===========================================");
            return 1;
        },executorService).thenApply((result) -> {
            System.out.println("第二个异步任务执行完毕...");
            System.out.println("使用得线程为:"+Thread.currentThread().getName());
            System.out.println("使用方法为:thenApply");
            System.out.println("===========================================");
            return result+1;
        }).thenApplyAsync(result -> {
            System.out.println("第三个异步任务执行完毕...");
            System.out.println("使用得线程为:"+Thread.currentThread().getName());
            System.out.println("使用方法为:thenApplyAsync");
            System.out.println("===========================================");
            return result+1;
        }).whenComplete((result,e) -> {
            System.out.println("所有异步任务执行完毕.");
        }).exceptionally(e -> {
            System.out.println("程序出现异常...");
            System.out.println(e.getCause());
            e.printStackTrace();
            return null;
        });

        executorService.shutdown();

输出结果:
在这里插入图片描述

3.源码分析

在这里插入图片描述
在这里插入图片描述
可以看到thenApplyAsync会使用一个默认得线程池.跟着继续往下走.
在这里插入图片描述
到这里就能很清楚得了解到区别了.

在这里插入图片描述

<think>我们正在讨论实际项目中使用CompletableFuture处理多个异步任务的方法。根据引用内容,CompletableFuture提供了多种方法来组合多个异步任务,特别是allOf方法用于等待所有任务完成。此外,还有anyOf方法用于获取最先完成的任务结果。同时,我们可以使用thenApply、thenAccept、thenRun等方法在任务完成后执行回调。 在实际项目中,我们通常需要考虑以下几点: 1. 创建异步任务:使用supplyAsync或runAsync方法,并可以指定线程池(若不指定则使用默认的ForkJoinPool)。 2. 组合多个任务:使用allOf或anyOf方法。 3. 处理任务结果:在allOf之后,我们可以通过原始Future对象获取每个任务的结果(注意:allOf返回的CompletableFuture<Void>不包含结果,需要分别获取)。 4. 异常处理:使用exceptionally或handle等方法处理异步任务中可能出现的异常。 下面是一个实际项目中的例子,模拟从不同服务异步获取数据,然后组合结果: ```java import java.util.concurrent.*; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class CompletableFutureExample { // 模拟异步获取商品图片信息 public static CompletableFuture<String> getProductImage() { return CompletableFuture.supplyAsync(() -> { // 模拟网络延迟 try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("查询商品的图片信息完成(线程: " + Thread.currentThread().getName() + ")"); return "image.jpg"; }); } // 模拟异步获取商品属性 public static CompletableFuture<String> getProductAttributes() { return CompletableFuture.supplyAsync(() -> { try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("查询商品的属性完成(线程: " + Thread.currentThread().getName() + ")"); return "黑色, 256G"; }); } // 模拟异步获取商品描述 public static CompletableFuture<String> getProductDescription() { return CompletableFuture.supplyAsync(() -> { try { TimeUnit.MILLISECONDS.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("查询商品描述完成(线程: " + Thread.currentThread().getName() + ")"); return "高性能智能手机"; }); } public static void main(String[] args) throws Exception { // 创建三个异步任务 CompletableFuture<String> futureImg = getProductImage(); CompletableFuture<String> futureAttr = getProductAttributes(); CompletableFuture<String> futureDesc = getProductDescription(); // 使用allOf组合三个任务,并等待所有任务完成 CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureImg, futureAttr, futureDesc); // 当所有任务完成后,获取每个任务的结果并组合 CompletableFuture<List<String>> combinedFuture = allFutures.thenApply(v -> { // 分别获取每个任务的结果(注意:此时任务已经完成,getNow不会阻塞) String image = futureImg.getNow("default.jpg"); String attributes = futureAttr.getNow(""); String description = futureDesc.getNow(""); return Arrays.asList(image, attributes, description); }); // 获取组合后的结果 List<String> results = combinedFuture.get(); System.out.println("组合结果: " + results); } } ``` 在上述代码中: 1. 我们创建了三个异步任务,分别获取商品图片、属性和描述。 2. 使用`CompletableFuture.allOf`等待所有任务完成。 3. 使用`thenApply`在所有任务完成后,将每个任务的结果组合成一个列表。注意,由于所有任务已经完成,我们可以使用`getNow`方法(非阻塞)获取结果。 4. 最后,我们通过`combinedFuture.get()`获取组合结果(这里会阻塞直到组合完成)。 注意事项: - 实际项目中,我们通常不会在主线程中调用`get()`方法,而是通过回调(如`thenAccept`)处理结果,以避免阻塞。 - 为了控制线程资源,建议根据实际情况使用自定义线程池。 - 异常处理:可以使用`exceptionally`在某个任务失败时提供默认值。 例如,添加异常处理: ```java CompletableFuture<String> futureAttr = getProductAttributes() .exceptionally(ex -> { System.out.println("获取属性失败,使用默认值"); return "默认属性"; }); ``` 这样,即使其中一个任务失败,整个组合任务也不会因为异常而中断,而是使用默认值继续。 §§相关问题§§ 1. 除了`allOf`,CompletableFuture还有哪些组合多个异步任务的方法? 2. 如何处理CompletableFuture组合任务中的异常? 3. 在实际项目中,如何选择使用默认线程池还是自定义线程池? </think>### 实际项目中使用CompletableFuture处理多个异步任务的方法 在Java实际项目中,`CompletableFuture`是处理异步任务组合的核心工具,特别适用于需要并行执行多个独立任务并聚合结果的场景。以下是关键方法与实践: #### 1. **等待所有任务完成(allOf)** 当需要所有异步任务执行完毕后再进行后续操作时,使用`allOf()`: ```java // 创建多个异步任务 CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> fetchDataFromServiceA()); CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> fetchDataFromServiceB()); // 组合任务并等待全部完成 CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2); allTasks.join(); // 阻塞直到所有任务完成 // 获取各任务结果 String result1 = task1.get(); Integer result2 = task2.get(); ``` - ✅ **适用场景**:商品详情页并行查询图片、属性、描述等信息[^2][^4] - ⚠️ 注意:`allOf()`返回`CompletableFuture<Void>`,需通过原始Future对象获取结果 #### 2. **任一任务完成触发(anyOf)** 当只需任意一个任务完成时触发后续操作: ```java CompletableFuture<Object> anyTask = CompletableFuture.anyOf(task1, task2); Object firstResult = anyTask.get(); // 获取最先返回的结果 ``` #### 3. **链式任务依赖(thenCompose/thenCombine)** - **顺序依赖**(前任务输出作为后任务输入): ```java task1.thenCompose(str -> CompletableFuture.supplyAsync(() -> processData(str)) ); ``` - **结果聚合**(双任务完成后合并结果): ```java task1.thenCombine(task2, (str, num) -> str + num) .thenAccept(System.out::println); ``` #### 4. **结果回调处理** - **结果消费**:`thenAccept()` ```java allTasks.thenRun(() -> System.out.println("所有任务完成,开始组装数据") ); ``` - **异常处理**:`exceptionally()` ```java task1.exceptionally(ex -> { log.error("任务失败", ex); return "default_value"; // 提供降级值 }); ``` #### 5. **线程池优化实践** - **自定义线程池**:避免使用默认ForkJoinPool导致资源竞争 ```java ExecutorService customPool = Executors.newFixedThreadPool(5); CompletableFuture.supplyAsync(() -> heavyTask(), customPool); ``` - **资源隔离**:不同业务类型使用独立线程池[^5] #### 6. **超时控制** Java 9+ 支持超时中断: ```java task1.orTimeout(500, TimeUnit.MILLISECONDS) .exceptionally(ex -> handleTimeout()); ``` ### ⚠️ 项目实践注意事项 1. **避免阻塞**:尽量使用`thenApply`/`thenAccept`替代`get()`/`join()` 2. **异常传播**:使用`handle()`统一处理成功/失败逻辑 3. **资源释放**:自定义线程池需显式`shutdown()` 4. **嵌套限制**:异步任务嵌套层级不超过3层,避免回调地狱 > **典型应用场景**:电商页面渲染(并行查询商品/库存/推荐)、微服务结果聚合、批量数据处理等[^1][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员bling

义父,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值