【Java异步编程】CompletableFuture实现:异步任务的串行执行

news2025/2/4 9:59:40

文章目录

    • 一. `thenApply()`:转换计算结果
      • 1. 一个线程中执行或多个线程中执行
      • 2. 使用场景说明
    • 二. `thenRun()`:执行无返回值的操作
      • 1. 语法说明
      • 2. 使用场景说明
    • 三. `thenAccept()`:消费计算结果
      • 1. 语法说明
        • a. 前后任务是否在一个线程中执行
        • b. 要点说明
      • 2. 使用场景说明
    • 四. `thenCompose()`:处理嵌套异步任务
      • 1. 语法说明
      • 2. 场景说明
    • 五. 总结
      • 1. thenCompose与thenApply的区别
      • 2. 四者区别

在 Java 异步编程中,CompletableFuture 类提供了多种方法来处理任务的依赖和执行顺序。理解这些方法如何协同工作,可以帮助我们更高效地处理复杂的异步操作。

一. thenApply():转换计算结果

thenApply() 方法允许我们在前一个 CompletableFuture 执行完成后,基于其计算结果执行转换操作,并返回一个新的 CompletableFuture。这个方法非常适合用于链式转换操作,例如数据转换或映射。

1. 一个线程中执行或多个线程中执行

三个重载方法如下:

     //后一个任务与前一个任务在同一个线程中执行
     public <U> CompletableFuture<U> thenApply(
                                 Function<? super T,? extends U> fn)
     
     //后一个任务与前一个任务不在同一个线程中执行
     public <U> CompletableFuture<U> thenApplyAsync(
                                 Function<? super T,? extends U> fn)
     
     //后一个任务在指定的executor线程池中执行 
     public <U> CompletableFuture<U> thenApplyAsync(
                                 Function<? super T,? extends U> fn, Executor executor)

可以看到thenApply可以将前后任务串到一个线程中执行,或者异步执行。

 

2. 使用场景说明

假设你从一个异步任务中获取了一个整数结果,而你需要对其进行一些计算或转换。通过 thenApply(),你可以轻松地对该结果进行处理。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2);
future.thenApply(result -> result * 2)  // result = 2, 返回结果是 4
      .thenAccept(result -> System.out.println(result));  // 输出 4

> **分析**> 
> - `CompletableFuture.supplyAsync(() -> 2)`:异步执行任务,返回结果 2> - `thenApply(result -> result * 2)`:对结果进行转换,返回 4> - `thenAccept(result -> System.out.println(result))`:消费结果,打印 4> 
> `thenApply()` 适用于当我们需要基于前一个任务的计算结果执行某种转换时,它帮助我们创建一个新的异步任务,并将处理后的结果返回。

 
再看一个例子:

@Test  
public void thenApplyDemo() throws Exception {  
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {  
        @Override  
        public Long get() {  
            long firstStep = 10L + 10L;  
            Print.tco("firstStep outcome is " + firstStep);  
  
            return firstStep;  
        }  
    }).thenApplyAsync(new Function<Long, Long>() {  
        @Override  
        public Long apply(Long firstStepOutCome) {  
            long secondStep = firstStepOutCome * 2;  
            Print.tco("secondStep outcome is " + secondStep);  
            return secondStep;  
        }  
    });  
  
    long result = future.get();  
    Print.tco(" future is " + future);  
    Print.tco(" outcome is " + result);  
}


[ForkJoinPool.commonPool-worker-9]:firstStep outcome is 20
[ForkJoinPool.commonPool-worker-9]:secondStep outcome is 40
[main]: future is java.util.concurrent.CompletableFuture@5b37e0d2[Completed normally]
[main]: outcome is 40
JVM退出钩子(定时和顺序任务线程池) starting.... 
JVM退出钩子(定时和顺序任务线程池)  耗时(ms): 0

 

二. thenRun():执行无返回值的操作

thenApply() 不同,thenRun() 方法用于在前一个 CompletableFuture 执行完毕后,执行一个没有返回值的操作。通常用于执行副作用操作比如打印日志、更新状态等

1. 语法说明

前后任务是否在一个线程中执行

     //后一个任务与前一个任务在同一个线程中执行
     public CompletionStage<Void> thenRun(Runnable action);
     
     //后一个任务与前一个任务不在同一个线程中执行
     public CompletionStage<Void> thenRunAsync(Runnable action);
     
     //后一个任务在executor线程池中执行
     public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

thenRun系列方法中的action参数是Runnable类型的,所以thenRun()既不能接收参数又不支持返回值。

 

2. 使用场景说明

假设你只关心任务完成后的某个动作,但不需要使用前一个任务的计算结果。thenRun() 正是为这种场景设计的。

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> 2);
future.thenRun(() -> System.out.println("Task finished"));  // 输出 "Task finished"



**分析**- `CompletableFuture.supplyAsync(() -> 2)`:异步任务返回结果 2,但我们并不关心它。
- `thenRun(() -> System.out.println("Task finished"))`:
- 在任务完成后执行无返回值的副作用操作,输出 "Task finished"

thenRun() 不需要前一个任务的结果,只是执行一个副作用操作。因此,它常用于在任务完成后进行一些收尾工作,比如清理资源、记录日志等。

 

三. thenAccept():消费计算结果

thenAccept() 方法与 thenApply() 类似,不同之处在于它不返回任何值,只是消费前一个 CompletableFuture 的结果。它通常用于当你只关心处理结果,而不需要转换它时。

1. 语法说明

a. 前后任务是否在一个线程中执行
     //后一个任务与前一个任务在同一个线程中执行
     public CompletionStage<Void> thenAccept(Consumer<? super T> action);
     
     //后一个任务与前一个任务不在同一个线程中执行
     public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
     
     //后一个任务在指定的executor线程池中执行
     public CompletionStage<Void> thenAcceptAsync(
                             Consumer<? super T> action,Executor executor);

 

b. 要点说明
     @FunctionalInterface
     public interface Consumer<T> {
         void accept(T t);
     }
  1. Consumer<T>接口的accept()方法可以接收一个参数,但是不支持返回值,所以thenAccept()可以将前一个任务的结果及该阶段性的结果通过void accept(T t)方法传递到下一个任务。

  2. Consumer<T>接口的accept()方法没有返回值,所以thenAccept()方法也不能提供第二个任务的执行结果。

 

2. 使用场景说明

假设你从异步任务中获取了一个值,你只需要打印它或记录它,而不需要对其进行转换或进一步操作。thenAccept() 就是为这种需求设计的。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2);
future.thenAccept(result -> System.out.println("Result: " + result));  // 输出 "Result: 2"


**分析**- `CompletableFuture.supplyAsync(() -> 2)`:异步执行任务,返回结果 2- `thenAccept(result -> System.out.println("Result: " + result))`:消费结果,打印 "Result: 2"

 

 

四. thenCompose():处理嵌套异步任务

thenCompose() 是一种用于组合多个异步任务的方法。当你需要基于前一个任务的结果返回另一个 CompletableFuture 时,thenCompose() 是最适合的选择。它避免了“回调地狱”(Callback Hell),允许我们将多个异步操作串联在一起。

 

1. 语法说明

     public <U> CompletableFuture<U> thenCompose(
                 Function<? super T, ? extends CompletionStage<U>> fn);
     
     public <U> CompletableFuture<U> thenComposeAsync(
                 Function<? super T, ? extends CompletionStage<U>> fn) ;
     
     public <U> CompletableFuture<U> thenComposeAsync(
                 Function<? super T, ? extends CompletionStage<U>> fn, 
                 Executor executor) ;

 

2. 场景说明

假设你需要先执行一个异步任务,获得它的结果后,再执行另一个基于该结果的异步任务。thenCompose() 就是解决这个问题的理想工具。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2);
future.thenCompose(result -> CompletableFuture.supplyAsync(() -> result * 2))
      .thenAccept(result -> System.out.println(result));  // 输出 4


**分析**- `CompletableFuture.supplyAsync(() -> 2)`:异步任务返回结果 2- `thenCompose(result -> CompletableFuture.supplyAsync(() -> result * 2))`:
- 基于前一个任务的结果,执行另一个异步任务,返回新的 `CompletableFuture`,结果是 4- `thenAccept(result -> System.out.println(result))`:消费最终结果,输出 4

通过 thenCompose(),我们可以将多个异步任务串联在一起,并将前一个任务的结果传递给下一个任务。它使得我们能够清晰地处理依赖链,并避免嵌套的回调函数。

 

例子2:

@Test  
public void thenComposeDemo() throws Exception {  
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {  
        @Override  
        public Long get() {  
            long firstStep = 10L + 10L;  
            Print.tco("firstStep outcome is " + firstStep);  
  
            return firstStep;  
        }  
    }).thenCompose(new Function<Long, CompletionStage<Long>>() {  
        @Override  
        public CompletionStage<Long> apply(Long firstStepOutCome) {  
            //组装了第二个子任务  
            return CompletableFuture.supplyAsync(new Supplier<Long>() {  
                @Override  
                public Long get() {  
                    long secondStep = firstStepOutCome * 2;  
                    Print.tco("secondStep outcome is " + secondStep);  
                    return secondStep;  
                }  
            });  
        }  
  
    });  
    long result = future.get();  
    Print.tco(" outcome is " + result);  
}

 

五. 总结

1. thenCompose与thenApply的区别

  1. thenCompose()返回的是包装了普通异步方法的CompletionStage任务实例,通过该实例还可以进行下一轮CompletionStage任务的调度和执行,比如可以持续进行CompletionStage链式(或者流式)调用。

  2. thenApply()的返回值则简单多了,直接就是第二个任务的普通异步方法的执行结果,它的返回类型与第二步执行的普通异步方法的返回类型相同,通过thenApply()所返回的值不能进行下一轮CompletionStage链式(或者流式)调用。

特性thenApply()thenCompose()
返回值返回一个新的 CompletableFuture<T>,其中 T 是转换后的结果类型。返回一个新的 CompletableFuture<U>,其中 U 是通过前一个 CompletableFuture 的结果生成的另一个 CompletableFuture
返回类型返回一个单一的值(即结果的转换)。返回一个嵌套的 CompletableFuture,通常用于链式异步任务。
作用对前一个 CompletableFuture 的结果进行转换并返回新的结果。使用前一个任务的结果去执行另一个异步任务,并将该异步任务的结果返回。
适用场景适用于需要对结果进行处理、转换或者映射时。适用于需要处理嵌套异步任务(即结果依赖于另一个异步任务)时。

 

2. 四者区别

CompletableFuture 提供了丰富的方法来管理异步任务之间的关系。通过理解和使用 thenApply()thenRun()thenAccept()thenCompose(),我们可以灵活地控制任务的执行顺序、结果的传递和副作用的执行。

  • thenApply():用于基于前一个任务的结果进行转换,并返回新的 CompletableFuture
  • thenRun():用于执行不依赖于结果的操作,常用于副作用处理。
  • thenAccept():用于消费前一个任务的结果,通常用于打印或记录日志。
  • thenCompose():用于处理依赖于前一个任务结果的嵌套异步任务。

 

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

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

相关文章

LabVIEW无人机航线控制系统

介绍了一种无人机航线控制系统&#xff0c;该系统利用LabVIEW软件与MPU6050九轴传感器相结合&#xff0c;实现无人机飞行高度、速度、俯仰角和滚动角的实时监控。系统通过虚拟仪器技术&#xff0c;有效实现了数据的采集、处理及回放&#xff0c;极大提高了无人机航线的控制精度…

AtCoder Beginner Contest 391(ABCDE)

A - Lucky Direction 翻译&#xff1a; 给你一个字符串 D&#xff0c;代表八个方向&#xff08;北、东、西、南、东北、西北、东南、西南&#xff09;之一。方向与其代表字符串之间的对应关系如下。 北&#xff1a; N东&#xff1a; E西&#xff1a; W南&#xff1a; S东…

MINIRAG: TOWARDS EXTREMELY SIMPLE RETRIEVAL-AUGMENTED GENERATION论文翻译

感谢阅读 注意不含评估以后的翻译原论文地址标题以及摘要介绍部分MiniRAG 框架2.1 HETEROGENEOUS GRAPH INDEXING WITH SMALL LANGUAGE MODELS2.2 LIGHTWEIGHT GRAPH-BASED KNOWLEDGE RETRIEVAL2.2.1 QUERY SEMANTIC MAPPING2.2.2 TOPOLOGY-ENHANCED GRAPH RETRIEVAL 注意不含评…

HTB:LinkVortex[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用gobuster对靶机进行路径FUZZ 使用ffuf堆靶机…

3D图形学与可视化大屏:什么是材质属性,有什么作用?

一、颜色属性 漫反射颜色 漫反射颜色决定了物体表面对入射光进行漫反射后的颜色。当光线照射到物体表面时&#xff0c;一部分光被均匀地向各个方向散射&#xff0c;形成漫反射。漫反射颜色的选择会直接影响物体在光照下的外观。例如&#xff0c;一个红色的漫反射颜色会使物体在…

什么是门控循环单元?

一、概念 门控循环单元&#xff08;Gated Recurrent Unit&#xff0c;GRU&#xff09;是一种改进的循环神经网络&#xff08;RNN&#xff09;&#xff0c;由Cho等人在2014年提出。GRU是LSTM的简化版本&#xff0c;通过减少门的数量和简化结构&#xff0c;保留了LSTM的长时间依赖…

基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)

酒店管理小程序目录 目录 基于微信小程序的酒店管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 (1) 用户信息管理 (2) 酒店管理员管理 (3) 房间信息管理 2、小程序序会员模块的实现 &#xff08;1&#xff09;系统首页 &#xff…

Java-数据结构-优先级队列(堆)

一、优先级队列 ① 什么是优先级队列&#xff1f; 在此之前&#xff0c;我们已经学习过了"队列"的相关知识&#xff0c;我们知道"队列"是一种"先进先出"的数据结构&#xff0c;我们还学习过"栈"&#xff0c;是"后进先出"的…

爬虫基础(四)线程 和 进程 及相关知识点

目录 一、线程和进程 &#xff08;1&#xff09;进程 &#xff08;2&#xff09;线程 &#xff08;3&#xff09;区别 二、串行、并发、并行 &#xff08;1&#xff09;串行 &#xff08;2&#xff09;并行 &#xff08;3&#xff09;并发 三、爬虫中的线程和进程 &am…

C语言初阶力扣刷题——349. 两个数组的交集【难度:简单】

1. 题目描述 力扣在线OJ题目 给定两个数组&#xff0c;编写一个函数来计算它们的交集。 示例&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,2] 输出&#xff1a;[2] 输入&#xff1a;nums1 [4,9,5], nums2 [9,4,9,8,4] 输出&#xff1a;[9,4] 2. 思路 直接暴力…

Sqoop导入MySQL中含有回车换行符的数据

个人博客地址&#xff1a;Sqoop导入MySQL中含有回车换行符的数据 MySQL中的数据如下图&#xff1a; 检查HDFS上的目标文件内容可以看出&#xff0c;回车换行符位置的数据被截断了&#xff0c;导致数据列错位。 Sqoop提供了配置参数&#xff0c;在导入时丢弃掉数据的分隔符&…

LightM-UNet(2024 CVPR)

论文标题LightM-UNet: Mamba Assists in Lightweight UNet for Medical Image Segmentation论文作者Weibin Liao, Yinghao Zhu, Xinyuan Wang, Chengwei Pan, Yasha Wang and Liantao Ma发表日期2024年01月01日GB引用> Weibin Liao, Yinghao Zhu, Xinyuan Wang, et al. Ligh…

stm32硬件实现与w25qxx通信

使用的型号为stm32f103c8t6与w25q64。 STM32CubeMX配置与引脚衔接 根据stm32f103c8t6引脚手册&#xff0c;采用B12-B15四个引脚与W25Q64连接&#xff0c;实现SPI通信。 W25Q64SCK&#xff08;CLK&#xff09;PB13MOSI&#xff08;DI&#xff09;PB15MISO(DO)PB14CS&#xff08…

FPGA 使用 CLOCK_DEDICATED_ROUTE 约束

使用 CLOCK_DEDICATED_ROUTE 约束 CLOCK_DEDICATED_ROUTE 约束通常在从一个时钟区域中的时钟缓存驱动到另一个时钟区域中的 MMCM 或 PLL 时使 用。默认情况下&#xff0c; CLOCK_DEDICATED_ROUTE 约束设置为 TRUE &#xff0c;并且缓存 /MMCM 或 PLL 对必须布局在相同…

一个开源 GenBI AI 本地代理(确保本地数据安全),使数据驱动型团队能够与其数据进行互动,生成文本到 SQL、图表、电子表格、报告和 BI

一、GenBI AI 代理介绍&#xff08;文末提供下载&#xff09; github地址&#xff1a;https://github.com/Canner/WrenAI 本文信息图片均来源于github作者主页 在 Wren AI&#xff0c;我们的使命是通过生成式商业智能 &#xff08;GenBI&#xff09; 使组织能够无缝访问数据&…

C动态库的生成与在Python和QT中的调用方法

目录 一、动态库生成 1&#xff09;C语言生成动态库 2&#xff09;c类生成动态库 二、动态库调用 1&#xff09;Python调用DLL 2&#xff09;QT调用DLL 三、存在的一些问题 1&#xff09;python调用封装了类的DLL可能调用不成功 2&#xff09;DLL格式不匹配的问题 四、…

C++ Primer 自定义数据结构

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中&#xff0c;同义词&#xff08;Synonym&#xff09;是对数…

PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统

基于YOLOv8深度学习的学生课堂行为检测识别系统&#xff0c;其能识别三种学生课堂行为&#xff1a;names: [举手, 读书, 写字] 具体图片见如下&#xff1a; 第一步&#xff1a;YOLOv8介绍 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本…

7.攻防世界fileclude

题目描述 进入题目页面如下 看到题目提示应该为文件包含漏洞 解释上述代码 // 输出提示信息&#xff1a;错误的方式&#xff01; WRONG WAY! <?php // 包含名为 "flag.php" 的文件&#xff0c;通常这个文件里可能包含重要的敏感信息&#xff0c;如 flag inclu…