优雅的并发编程-CompletableFuture

2023-11-09 09:37:33 浏览数 (2)

目录

了解CompletableFuture

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

CompletableFuture使用场景

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

多线程任务使用案例

我将使用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使用案例
代码语言:javascript复制
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());
    }
}
业务优化案例

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

代码语言:javascript复制
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来优化代码,提高接口响应速度。

代码语言:javascript复制
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的相关实现原理,请您留个关注,明日更新。

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

0 人点赞