目录
了解CompletableFuture
CompletableFuture 是 Java 8 引入的一个类,用于支持异步编程和非阻塞操作。它提供了一种简单而强大的方式来处理异步任务,可以轻松地实现并行、非阻塞的操作,并且提供了丰富的方法来处理任务的完成状态、异常情况以及多个任务之间的串联和组合。
CompletableFuture使用场景
- 并行处理多个独立任务:当一个任务可以被分解为多个独立的子任务时,可以使用CompletableFuture来并行执行这些子任务,从而提高系统的性能和响应速度。比如,在电商系统中,查询用户信息、订单信息、购物车信息等可以并行执行,然后在所有子任务完成后进行结果合并。
- 异步执行耗时操作:对于一些耗时的操作,比如远程调用、数据库查询等,可以使用CompletableFuture来异步执行这些操作,避免阻塞主线程,提高系统的吞吐量和并发能力。
- 组合多个异步任务的结果:有时候需要等待多个异步任务都完成后才能进行下一步处理,可以使用CompletableFuture的组合方法(比如thenCombine、thenCompose等)来等待多个异步任务的结果,并在所有任务完成后进行处理。
- 超时处理和异常处理:CompletableFuture提供了丰富的异常处理和超时处理的方法,可以很方便地处理异步任务执行过程中出现的异常或者超时情况。
- 实现异步回调:通过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使用案例
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在线程池中执行时,可能会出现代码异常,但是并没有将异常抛出的情况,原因有二:
- 在
CompletableFuture
的异步任务中,如果出现异常而没有显式地处理或抛出,那么这个异常就会被吞噬,不会传播到CompletableFuture
的最终结果上。 - 另一种可能是因为异步任务在执行时发生了异常,但是在后续对
CompletableFuture
的处理过程中,没有正确处理这些异常。例如,在使用thenApply
、exceptionally
等方法对CompletableFuture
的结果进行处理时,没有考虑到可能存在的异常情况,导致异常被掩盖。
解决方案
为了解决这个问题,你可以在异步任务的代码中,适当地处理异常并进行抛出,或者使用exceptionally
方法来处理异常情况,确保异常能够正确地传播并得到处理。此外,在对CompletableFuture
的结果进行处理时,需要注意处理可能发生的异常情况,以确保异常能够被及时捕获和处理。
CompletableFuture优缺点分析
CompletableFuture
是 Java 8 开始引入的一个用于支持异步编程的工具类,它提供了丰富的 API 来简化异步编程,并提供了对多个异步操作的组合、串行和并行执行的支持。下面是 CompletableFuture 的一些优缺点分析:
优点:
- 简化异步编程:
CompletableFuture
提供了简洁的 API,使得异步编程变得更加直观和易于理解。它允许开发者在不同的阶段对异步任务的结果进行处理、转换,甚至是以函数式编程的方式进行流式处理。 - 异步任务组合:
CompletableFuture
支持多个异步任务的组合、串行和并行执行,可以通过thenCompose
、thenCombine
等方法灵活地组织复杂的异步任务流程。 - 异常处理:
CompletableFuture
提供了异常处理机制,可以通过exceptionally
方法对异步任务执行过程中的异常进行处理,从而保证异常能够被正确捕获和处理。 - 超时处理:
CompletableFuture
支持设置异步任务的超时时间,可以通过 orTimeout 方法指定超时时间,防止任务长时间执行导致阻塞。 - 与函数式编程结合:
CompletableFuture
运用了函数式编程的思想,使得异步任务的处理更加灵活,并且可以与 Stream 接口等其他函数式编程工具结合使用。
缺点:
- 学习曲线:对于初学者来说,CompletableFuture 的 API 可能会有一定的学习曲线,特别是对于那些不熟悉函数式编程和异步编程模型的开发者来说。
- 过度使用复杂性:在一些简单的场景下,使用
CompletableFuture
可能会显得过于复杂,特别是在一些简单的线性任务处理中,可能会显得比较繁琐。 - 调试困难:由于
CompletableFuture
支持异步任务的组合和串行/并行执行,当出现逻辑错误或异常时,可能需要仔细追踪CompletableFuture
链中的每个环节,以确定问题的所在,这可能会增加调试的难度。
综上所述,CompletableFuture
作为 Java 异步编程的利器,提供了丰富的功能和灵活的操作方式,但在使用时需要根据实际情况权衡其优缺点,避免过度复杂化简单的任务处理,并合理处理异常情况以及确保代码的可读性和可维护性。
关于CompletableFuture
的相关实现原理,请您留个关注,明日更新。
感谢您看到最后,希望能解决您的困扰和疑惑,这是我更新最大的动力。
关于我
👋🏻你好,我是Debug.c。微信公众号:种颗代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。
🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。
📞如果您对我感兴趣,请联系我。
若有收获,就点个赞吧