前言
在Web应用开发中,一个界面可能需要同时请求多个接口来获取不同信息。传统的做法是编写一个聚合接口同步获取这些数据,第二种方法是分多次请求来获取数据。这两种方式虽然简单直观,但效率比较低下,随着应用复杂度的增加,这种低效的做法将会带来严重的性能问题。
异步编程模型可以很好地解决这个问题。多个任务可以同时执行,互不影响,从而大幅提高应用的响应速度和吞吐量。Java 8 中引入的CompletableFuture为异步编程提供了强有力的支持,使得编写异步代码变得更加简单。本文将重点介绍如何利用CompletableFuture优化并发查询接口的响应速度。
实现思路:
要优化并发查询接口的响应速度,传统的优化方式是通过多线程来并行执行多个查询任务。但这种做法存在一些缺陷:1.创建和管理线程的开销较大,如果线程数量过多,会给系统带来很大的压力。
2.如果查询任务的执行时间不均匀,会导致部分线程需要长时间等待,资源利用率低下。
而CompletableFuture提供了一种更优雅、更高效的解决方案。其核心思路是:
每个查询任务都封装为一个CompletableFuture异步任务,由线程池并行执行。
通过CompletableFuture.allOf()方法等待所有异步任务完成。
最后从每个任务的结果中组装出最终需要的数据对象。
一:创建CompetableFuture
// 从一个供给函数创建 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello"); // 从一个运行函数创建 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> System.out.println("Hello")); // 从一个已有的结果创建 CompletableFuture<String> future = CompletableFuture.completedFuture("Hello");二.链式调用
CompletableFuture<String> resultFuture = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(s -> s + " World") // 对结果进行转换 .thenCompose(s -> getResult(s)); // 组合另一个异步操作三. 异常处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (true) { throw new RuntimeException("Computation error!"); } return "hello!"; }).exceptionally(ex -> { System.out.println(ex.toString());// CompletionException return "world!"; }); assertEquals("world!", future.get());四。组合多个completablefuture的结果
// 等待所有任务完成 CompletableFuture.allOf(future1, future2, future3).get(); CompletableFuture.allOf(future1, future2, future3).join(); // 只要任意一个任务完成即可 CompletableFuture.anyOf(future1, future2, future3).get(); CompletableFuture.anyOf(future1, future2, future3).join(); // 规定超时时间,防止一直堵塞 CompletableFuture.allOf(future1, future2, future3).get(6, TimeUnit.SECONDS);五.设置超时时间
String result = CompletableFuture.supplyAsync(() -> "Hello") .completeOnTimeout("Timeout!", 1, TimeUnit.SECONDS) .get();- 我们上面的代码示例中,为了方便,都没有选择自定义线程池。实际项目中,这是不可取的。
CompletableFuture默认使用全局共享的ForkJoinPool.commonPool()作为执行器,所有未指定执行器的异步任务都会使用该线程池。这意味着应用程序、多个库或框架(如 Spring、第三方库)若都依赖CompletableFuture,默认情况下它们都会共享同一个线程池。
虽然ForkJoinPool效率很高,但当同时提交大量任务时,可能会导致资源竞争和线程饥饿,进而影响系统性能。
为避免这些问题,建议为CompletableFuture提供自定义线程池。
private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); CompletableFuture.runAsync(() -> { //... }, executor);CompletableFuture的get()方法是阻塞的,尽量避免使用。如果必须要使用的话,需要添加超时时间,否则可能会导致主线程一直等待,无法执行其他任务。
实战代码演示:
下面我会围绕电商、数据查询、接口聚合等高频业务场景,给出可直接运行的代码示例,并解释每个场景的核心价值。
场景 1:电商商品详情页 - 并行查询多维度数据
业务背景:商品详情页需要展示商品基本信息、库存、价格、用户评价摘要等数据,这些数据分散在不同的 DAO / 服务中,若串行查询会导致接口响应慢。核心价值:用 CompletableFuture 并行执行多个查询任务,汇总结果,大幅缩短接口响应时间。
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; // 模拟商品相关的服务 class ProductService { // 查询商品基本信息(模拟耗时100ms) public String getBaseInfo(Long productId) { try { Thread.sleep(100); } catch (InterruptedException e) {} return "商品ID:" + productId + ",名称:小米14,分类:手机"; } // 查询商品库存(模拟耗时80ms) public Integer getStock(Long productId) { try { Thread.sleep(80); } catch (InterruptedException e) {} return 1000; } // 查询商品价格(模拟耗时120ms) public Double getPrice(Long productId) { try { Thread.sleep(120); } catch (InterruptedException e) {} return 3999.0; } // 查询商品评价摘要(模拟耗时150ms) public String getCommentSummary(Long productId) { try { Thread.sleep(150); } catch (InterruptedException e) {} return "好评率98%,累计评价10w+"; } } public class ProductDetailDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ProductService service = new ProductService(); Long productId = 1001L; // 1. 异步并行执行多个查询任务 CompletableFuture<String> baseInfoFuture = CompletableFuture.supplyAsync(() -> service.getBaseInfo(productId)); CompletableFuture<Integer> stockFuture = CompletableFuture.supplyAsync(() -> service.getStock(productId)); CompletableFuture<Double> priceFuture = CompletableFuture.supplyAsync(() -> service.getPrice(productId)); CompletableFuture<String> commentFuture = CompletableFuture.supplyAsync(() -> service.getCommentSummary(productId)); // 2. 等待所有任务完成(总耗时≈最长的那个任务耗时,而非累加:150ms左右) CompletableFuture.allOf(baseInfoFuture, stockFuture, priceFuture, commentFuture).join(); // 3. 获取所有结果并组装 String baseInfo = baseInfoFuture.get(); Integer stock = stockFuture.get(); Double price = priceFuture.get(); String comment = commentFuture.get(); // 4. 输出结果 System.out.println("商品详情:"); System.out.println(baseInfo); System.out.println("库存:" + stock + "件"); System.out.println("价格:¥" + price); System.out.println("评价:" + comment); } }执行结果:
商品详情: 商品ID:1001,名称:小米14,分类:手机 库存:1000件 价格:¥3999.0 评价:好评率98%,累计评价10w+- 串行执行总耗时:100+80+120+150=450ms;并行执行仅≈150ms,响应速度提升 3 倍。
supplyAsync默认使用 ForkJoinPool 线程池,实际要指定自定义线程池避免核心线程被占满。
场景 2:异步任务依赖编排 - 先查用户再查订单
业务背景:需要先根据用户 ID 查询用户信息,再用用户信息中的会员等级查询该用户的专属订单(任务有依赖关系)。核心价值:用 CompletableFuture 的thenApply/thenCompose实现异步任务的串行依赖,避免主线程阻塞
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; // 模拟用户服务和订单服务 class UserService { // 查询用户信息(返回用户ID+会员等级) public User getUser(Long userId) { try { Thread.sleep(100); } catch (InterruptedException e) {} return new User(userId, "张三", "VIP3"); } } class OrderService { // 根据用户ID和会员等级查询专属订单 public String getVipOrders(Long userId, String vipLevel) { try { Thread.sleep(150); } catch (InterruptedException e) {} return "用户" + userId + "(" + vipLevel + ")的专属订单:[OD1001, OD1002, OD1003]"; } } // 用户实体类 class User { private Long userId; private String userName; private String vipLevel; public User(Long userId, String userName, String vipLevel) { this.userId = userId; this.userName = userName; this.vipLevel = vipLevel; } // getter public Long getUserId() { return userId; } public String getVipLevel() { return vipLevel; } } public class DependentTaskDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { UserService userService = new UserService(); OrderService orderService = new OrderService(); Long userId = 10086L; // 1. 第一步:异步查询用户信息 CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.getUser(userId)); // 2. 第二步:依赖用户信息,异步查询专属订单(thenCompose用于异步任务依赖) CompletableFuture<String> orderFuture = userFuture.thenCompose(user -> CompletableFuture.supplyAsync(() -> orderService.getVipOrders(user.getUserId(), user.getVipLevel())) ); // 3. 获取最终结果 String result = orderFuture.get(); System.out.println(result); } }thenCompose与thenApply的区别:thenApply接收同步函数,thenCompose接收返回 Future 的异步函数,适合多步异步依赖。- 整个流程异步执行,主线程无需等待第一步完成再执行第二步,充分利用线程资源。
thenApply/thenCompose实现的异步串行依赖,不会缩短单个请求的任务总耗时,但能解放主线程,提升系统整体的并发响应能力;耗时的串行依赖任务 → 用thenCompose保证真异步,避免阻塞前序任务线程;
对比
当多个任务的执行不需要依赖彼此的结果,每个任务的输入都是独立的(比如仅依赖初始的入参,而非其他任务的输出),执行顺序不影响最终结果。能直接缩短总耗时。
其他的需要依赖彼此结果的,比如要先查到用户id,才能去查用户的订单详情。这种情况不能直接缩短耗时,但能提高并发量。因为是异步执行的,主线程不会阻塞。
总结:
CompletableFuture为Java提供了强大的异步编程能力,可以极大地提高应用的并发能力和响应速度。通过并行执行多个查询任务,我们可以大幅减少接口的响应时间,优化用户体验。同时,CompletableFuture的代码风格函数式、简洁、优雅,也使得代码更加易读易维护。
但是,异步编程也不是万能的,它需要开发者转变思维模式,还需要权衡利弊。在实际项目中,我们可以结合其他优化手段,选择合适的方案,以达到最佳的性能效果。