优雅的并发编程-CompletableFuture

news2024/12/23 5:28:30

目录

了解CompletableFuture

CompletableFuture 是 Java 8 引入的一个类,用于支持异步编程和非阻塞操作。它提供了一种简单而强大的方式来处理异步任务,可以轻松地实现并行、非阻塞的操作,并且提供了丰富的方法来处理任务的完成状态、异常情况以及多个任务之间的串联和组合。

CompletableFuture使用场景

  1. 并行处理多个独立任务:当一个任务可以被分解为多个独立的子任务时,可以使用CompletableFuture来并行执行这些子任务,从而提高系统的性能和响应速度。比如,在电商系统中,查询用户信息、订单信息、购物车信息等可以并行执行,然后在所有子任务完成后进行结果合并。
  2. 异步执行耗时操作:对于一些耗时的操作,比如远程调用、数据库查询等,可以使用CompletableFuture来异步执行这些操作,避免阻塞主线程,提高系统的吞吐量和并发能力。
  3. 组合多个异步任务的结果:有时候需要等待多个异步任务都完成后才能进行下一步处理,可以使用CompletableFuture的组合方法(比如thenCombine、thenCompose等)来等待多个异步任务的结果,并在所有任务完成后进行处理。
  4. 超时处理和异常处理:CompletableFuture提供了丰富的异常处理和超时处理的方法,可以很方便地处理异步任务执行过程中出现的异常或者超时情况。
  5. 实现异步回调:通过CompletableFuture的回调方法,可以在异步任务完成后执行特定的逻辑,比如通知其他系统、记录日志等。

pexels-umut-sarıalan-18038272.jpg

多线程任务使用案例

我将使用CompletableFuture的以下API来说明CompletableFuture的使用方法。

  • supplyAsync:启动一个异步任务,并返回一个CompletableFuture对象,该任务会在传入的Supplier中执行。在这个例子中,第一个CompletableFuture启动了一个异步任务,模拟了一个耗时的操作,然后返回一个整数结果。
  • exceptionally:处理上一个阶段执行过程中出现的异常,返回一个默认值。这里的result2使用exceptionally方法来处理result1阶段的异常,如果result1出现异常,就返回一个包含异常信息的字符串。
  • thenApply:对上一个阶段的结果进行处理,并返回一个新的CompletableFuture对象。在这个例子中,result1是通过对future1的结果进行处理得到的,将异步任务1的结果转换为字符串并添加额外的处理。
  • exceptionally:处理上一个阶段执行过程中出现的异常,返回一个默认值。这里的result2使用exceptionally方法来处理result1阶段的异常,如果result1出现异常,就返回一个包含异常信息的字符串。
  • orTimeout:设置异步任务的超时时间。在这个例子中,result3使用orTimeout方法来设置任务的超时时间为2分钟,如果任务未在指定时间内完成,将抛出TimeoutException。
  • runAsync:启动一个异步任务,但不返回任何结果。在这个例子中,result4表示一个没有返回值的异步任务,只是简单地打印一条信息。
  • allOf:等待所有的CompletableFuture执行完毕。在这个例子中,通过使用allOf方法,等待result2、result3和result4都完成后再继续执行后续的逻辑。
  • join:等待CompletableFuture的完成并获取结果。在这个例子中,通过调用join方法等待所有任务完成,并获取它们的结果。

API使用案例

public class CompletableFutureExample {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 异步任务启动:supplyAsync
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1开始执行");
            try {
                //模拟执行耗时操作
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            return 100;
        });

        // 异步任务结果处理:thenApply
        CompletableFuture<String> result1 = future1.thenApply(value -> {
            System.out.println("任务1结果处理: " + value);
            return "Processed Result1: " + value;
        });

        // 串联/组合多个任务的处理结果:exceptionally
        CompletableFuture<String> result2 = result1.exceptionally(ex -> "任务1出现异常: " + ex.getMessage());

        // 超时处理:orTimeout
        CompletableFuture<String> result3 = CompletableFuture.supplyAsync(() -> {
            try {
                //模拟执行耗时操作
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            return "任务2完成";
        }).orTimeout(2, TimeUnit.MINUTES);

        // 流处理/函数式编程:与 Stream 接口结合
        CompletableFuture<Void> result4 = CompletableFuture.runAsync(() -> {
            System.out.println("任务3开始执行");
        });

        // 等待所有任务完成
        CompletableFuture.allOf(result2, result3, result4).join();

        System.out.println(result2.get());
        System.out.println(result3.get());
    }
}

业务优化案例

假如你是一名高级高级工程师,你们项目组正在做电商相关的业务,对接口的响应速度要求较高,现在有个需求是需要同时查询出:用户的账号信息、订单信息、购物车信息、银行卡支付信息,如果没有并发思想,那么接口的视线逻辑大致是这样的:

public class ECommerceService {
    public UserAccount getUserAccount() {
        // 执行查询用户账号信息的逻辑
        UserAccount userAccount = // ...;
        return userAccount;
    }

    public OrderInfo getOrderInfo() {
        // 执行查询订单信息的逻辑
        OrderInfo orderInfo = // ...;
        return orderInfo;
    }

    public ShoppingCart getShoppingCart() {
        // 执行查询购物车信息的逻辑
        ShoppingCart shoppingCart = // ...;
        return shoppingCart;
    }

    public BankCardPaymentInfo getBankCardPaymentInfo() {
        // 执行查询银行卡支付信息的逻辑
        BankCardPaymentInfo bankCardPaymentInfo = // ...;
        return bankCardPaymentInfo;
    }

    public void processECommerceRequest() {
        UserAccount userAccount = getUserAccount();
        OrderInfo orderInfo = getOrderInfo();
        ShoppingCart shoppingCart = getShoppingCart();
        BankCardPaymentInfo bankCardPaymentInfo = getBankCardPaymentInfo();

        // 在这里合并处理各个查询结果
        // ...
    }
}

以上是接口串行执行的逻辑,当前服务器环境一般都是多核心、多线程,服务器可以同时处理多个任务,如果此时还使用单线程去执行,有些“暴殄天物”,并且接口响应速度会比较慢,那么优化的方式就是开启多个线程去分别执行不同的逻辑,那么我们可以使用CompletableFuture来优化代码,提高接口响应速度。

import java.util.concurrent.CompletableFuture;

public class ECommerceService {
    public CompletableFuture<UserAccount> getUserAccountAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // 执行查询用户账号信息的逻辑
            UserAccount userAccount = // ...;
            return userAccount;
        });
    }

    public CompletableFuture<OrderInfo> getOrderInfoAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // 执行查询订单信息的逻辑
            OrderInfo orderInfo = // ...;
            return orderInfo;
        });
    }

    public CompletableFuture<ShoppingCart> getShoppingCartAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // 执行查询购物车信息的逻辑
            ShoppingCart shoppingCart = // ...;
            return shoppingCart;
        });
    }

    public CompletableFuture<BankCardPaymentInfo> getBankCardPaymentInfoAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // 执行查询银行卡支付信息的逻辑
            BankCardPaymentInfo bankCardPaymentInfo = // ...;
            return bankCardPaymentInfo;
        });
    }

    public CompletableFuture<Void> processECommerceRequest() {
        CompletableFuture<UserAccount> userAccountFuture = getUserAccountAsync();
        CompletableFuture<OrderInfo> orderInfoFuture = getOrderInfoAsync();
        CompletableFuture<ShoppingCart> shoppingCartFuture = getShoppingCartAsync();
        CompletableFuture<BankCardPaymentInfo> bankCardPaymentInfoFuture = getBankCardPaymentInfoAsync();

        return CompletableFuture.allOf(userAccountFuture, orderInfoFuture, shoppingCartFuture, bankCardPaymentInfoFuture)
            .thenAcceptAsync((Void) -> {
                UserAccount userAccount = userAccountFuture.join();
                OrderInfo orderInfo = orderInfoFuture.join();
                ShoppingCart shoppingCart = shoppingCartFuture.join();
                BankCardPaymentInfo bankCardPaymentInfo = bankCardPaymentInfoFuture.join();

                // 在这里合并处理各个异步任务的结果
                // ...
            });
    }
}

以上是账号、订单、购物车、银行卡支付逻辑块各开启一个线程异步去执行,最后等待各个线程执行完毕汇总结果,提高接口影响速度。

单线程和多线程相比,举个不恰当的例子:当你为了报仇,踌躇满志上山叩拜师门,经过十年寒窗苦练、不舍昼夜的练习,终于学成归来下山复仇,结果仇家拿出AK 47一阵突突,云淡风轻的跟你说:“大人,时代变了”的无力感。

一点小提示

问题

CompletableFuture在线程池中执行时,可能会出现代码异常,但是并没有将异常抛出的情况,原因有二:

  1. CompletableFuture的异步任务中,如果出现异常而没有显式地处理或抛出,那么这个异常就会被吞噬,不会传播到CompletableFuture的最终结果上。
  2. 另一种可能是因为异步任务在执行时发生了异常,但是在后续对CompletableFuture的处理过程中,没有正确处理这些异常。例如,在使用thenApplyexceptionally等方法对CompletableFuture的结果进行处理时,没有考虑到可能存在的异常情况,导致异常被掩盖。

解决方案

为了解决这个问题,你可以在异步任务的代码中,适当地处理异常并进行抛出,或者使用exceptionally方法来处理异常情况,确保异常能够正确地传播并得到处理。此外,在对CompletableFuture的结果进行处理时,需要注意处理可能发生的异常情况,以确保异常能够被及时捕获和处理。

CompletableFuture优缺点分析

CompletableFuture 是 Java 8 开始引入的一个用于支持异步编程的工具类,它提供了丰富的 API 来简化异步编程,并提供了对多个异步操作的组合、串行和并行执行的支持。下面是 CompletableFuture 的一些优缺点分析:

优点:

  1. 简化异步编程CompletableFuture 提供了简洁的 API,使得异步编程变得更加直观和易于理解。它允许开发者在不同的阶段对异步任务的结果进行处理、转换,甚至是以函数式编程的方式进行流式处理。
  2. 异步任务组合CompletableFuture 支持多个异步任务的组合、串行和并行执行,可以通过 thenComposethenCombine 等方法灵活地组织复杂的异步任务流程。
  3. 异常处理CompletableFuture 提供了异常处理机制,可以通过 exceptionally 方法对异步任务执行过程中的异常进行处理,从而保证异常能够被正确捕获和处理。
  4. 超时处理CompletableFuture 支持设置异步任务的超时时间,可以通过 orTimeout 方法指定超时时间,防止任务长时间执行导致阻塞。
  5. 与函数式编程结合CompletableFuture 运用了函数式编程的思想,使得异步任务的处理更加灵活,并且可以与 Stream 接口等其他函数式编程工具结合使用。

缺点:

  1. 学习曲线:对于初学者来说,CompletableFuture 的 API 可能会有一定的学习曲线,特别是对于那些不熟悉函数式编程和异步编程模型的开发者来说。
  2. 过度使用复杂性:在一些简单的场景下,使用 CompletableFuture 可能会显得过于复杂,特别是在一些简单的线性任务处理中,可能会显得比较繁琐。
  3. 调试困难:由于 CompletableFuture 支持异步任务的组合和串行/并行执行,当出现逻辑错误或异常时,可能需要仔细追踪 CompletableFuture 链中的每个环节,以确定问题的所在,这可能会增加调试的难度。

综上所述,CompletableFuture 作为 Java 异步编程的利器,提供了丰富的功能和灵活的操作方式,但在使用时需要根据实际情况权衡其优缺点,避免过度复杂化简单的任务处理,并合理处理异常情况以及确保代码的可读性和可维护性。


关于CompletableFuture的相关实现原理,请您留个关注,明日更新。

感谢您看到最后,希望能解决您的困扰和疑惑,这是我更新最大的动力。

关于我

👋🏻你好,我是Debug.c。微信公众号:种颗代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。

🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。

📞如果您对我感兴趣,请联系我。

若有收获,就点个赞吧

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

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

相关文章

iOS加固原理与常见措施:保护移动应用程序安全的利器

​ 目录 iOS加固原理与常见措施&#xff1a;保护移动应用程序安全的利器 前言 一、iOS加固的原理 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 二、iOS加固的常见措施 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 三、iOS加固的效果和注意事项 参…

什么是代理IP池?如何判断IP池优劣?

代理池充当多个代理服务器的存储库&#xff0c;提供在线安全和匿名层。代理池允许用户抓取数据、访问受限制的内容以及执行其他在线任务&#xff0c;而无需担心被检测或阻止的风险。代理池为各种在线活动&#xff08;例如网页抓取、安全浏览等&#xff09;提高后勤保障。 读完…

一个“Hello, World”Flask应用程序

如果您访问Flask网站&#xff0c;会看到一个非常简单的示例应用程序&#xff0c;只有5行代码。为了不重复那个简单的示例&#xff0c;我将向您展示一个稍微复杂一些的示例&#xff0c;它将为您编写大型应用程序提供一个良好的基础结构。 应用程序将存在于包中。在Python中&…

三大基础排序 -选择排序、冒泡排序、插入排序

排序算法 文章目录 冒泡排序算法步骤动图代码优化总结 选择排序算法步骤动图代码总结 插入排序算法步骤动图代码总结 排序算法&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。一般默认排序是按照由小到大即…

【JVM】运行时数据区、程序计数器

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、 运行时数据区二、 程序计数器程序…

Console 线连接路由器交换机等设备

Console 线连接路由器交换机等设备 Console 线几乎是每一个网工必备的&#xff0c;学会使用 Console 线去连接真实的设备也是非常重要的。这里我们就使用 XShell 软件来演示设备的连接和管理配置。 文章目录 Console 线连接路由器交换机等设备一、Console 线二、连接设备 Cons…

2023-11-Rust

学习方案&#xff1a;Rust程序设计指南 1、变量和可变性 声明变量&#xff1a;let 变量、const 常量 rust 默认变量一旦声明&#xff0c;就不可变(immutable)。当想改变 加 mut&#xff08;mutable&#xff09; 。 const 不允许用mut &#xff0c;只能声明常量&#xff0c;…

详解静态成员变量以及静态成员函数

一、静态成员变量 类的静态成员变量是该类的所有对象共有的(只有一份)&#xff0c;只能在类里声明&#xff0c;类外定义。 相当于只属于类的全局变量。 1、定义: 只能在全局中定义 2、访问方式&#xff1a;(假如类A 中有公有静态变量 _a) &#xff0c;可以用 A::_a 或 A a; a._…

大数据毕业设计选题推荐-市天气预警实时监控平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

华为eNSP实验-DHCP实验(简易入门版)

1.拓扑图 2.R1配置 <Huawei>system-view [Huawei]sysname R1 [R1]interface GigabitEthernet 0/0/0 [R1-GigabitEthernet0/0/0]ip address 192.168.1.1 24 [R1]ip pool PC [R1-ip-pool-PC]gateway-list 192.168.1.1 [R1-ip-pool-PC]network 192.168.1.0 mask 24 [R1-i…

基于鱼鹰算法的无人机航迹规划-附代码

基于鱼鹰算法的无人机航迹规划 文章目录 基于鱼鹰算法的无人机航迹规划1.鱼鹰搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用鱼鹰算法来优化无人机航迹规划。 1.鱼鹰搜索算法 …

RabbitMQ集群配置以及负载均衡配置

RabbitMQ集群配置以及负载均衡配置 环境配置集群配置安装rabbitmq启动rabbitmq开启远程登录添加用户并且授权用户添加数据存放目录和日志存放目录查看端口拷⻉erlang.cookie将mq-2、mq-3作为内存节点加⼊mq-1节点集群中查看集群状态添加一个新的队列 RabbitMq负载均衡配置-HAPr…

Python算法例8 将整数A转换为B

1. 问题描述 给定整数A和B&#xff0c;求出将整数A转换为B&#xff0c;需要改变bit的位数。 2. 问题示例 把31转换为14&#xff0c;需要改变2个bit位&#xff0c;即&#xff1a;&#xff08;31&#xff09;10&#xff08;11111&#xff09;2&#xff0c;&#xff08;14&…

一篇文章带你使用(MMKV--基于 mmap 的高性能通用 key-value 组件)

一、MMKV是什么&#xff1f; MMKV 是基于 mmap 内存映射的 key-value 组件&#xff0c;底层序列化/反序列化使用 protobuf 实现&#xff0c;性能高&#xff0c;稳定性强。也是腾讯微信团队使用的技术。 支持的数据类型 支持以下 Java 语言基础类型&#xff1a; boolean、int…

linux安装jdk和weblogic易错点

1.版本问题&#xff0c;如果版本不兼容&#xff0c;安装的时候会报错&#xff0c;所有安装之前要确认好版本 jdk1.6&#xff0c;weblogic10 2.jdk安装后配置文件 JAVA_HOME ,CLASSPATH,PATH&#xff0c;配置问你的追加&#xff0c;用冒号链接 修改后需要用source 刷新下 3安装…

SpringCloudAlibaba系列之Nacos配置管理

目录 说明 认识配置中心 Nacos架构图 Nacos配置管理实现原理 核心源码分析-客户端 核心源码分析-服务端 配置修改的实时通知 主流配置中心对比 小小收获 说明 本篇文章主要目的是从头到尾比较粗粒度的分析Nacos配置中心的一些实现&#xff0c;很多细节没有涉及&#…

接口---默认方法

用户操作界面 package Default;public class Dome02interface {public static void main(String[] args) {// 创建实现类对象 // MyInterfaceDefaultA A new MyInterfaceDefaultA(); // A.method01(); // System.out.println("--------------"); // 调用默认方…

Linux下找出吃内存的方法

几个 个 Linux 内存查看方法 1、free命令 2、 vmstat命令 3、 /proc/meminfo 命令 4、 top命令 5、 htop 命令 6、查看进程内存信息 内存性能指标 系统内存使用情况&#xff0c;比如已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量等。 共享内存是通过 tmp…

RISC-V处理器设计(五)—— 在 RISC-V 处理器上运行 C 程序

目录 一、前言 二、从 C 程序到机器指令 三、实验 3.1 实验环境 3.11 Windows 平台下环境搭建 3.12 Ubuntu 平台下环境搭建 3.13 实验涉及到的代码或目录 3.2 各文件作用介绍 3.2.1 link.lds 3.2.2 start.S 3.2.3 lib 和 include 目录 3.2.4 common.mk 3.2.5 demo …