接口响应慢到崩溃?CompletableFuture 并行编排让效率提升 3 倍
前言在Web应用开发中,一个界面可能需要同时请求多个接口来获取不同信息。传统的做法是编写一个聚合接口同步获取这些数据第二种方法是分多次请求来获取数据。这两种方式虽然简单直观,但效率比较低下随着应用复杂度的增加,这种低效的做法将会带来严重的性能问题。异步编程模型可以很好地解决这个问题。多个任务可以同时执行,互不影响,从而大幅提高应用的响应速度和吞吐量。Java 8 中引入的CompletableFuture为异步编程提供了强有力的支持,使得编写异步代码变得更加简单。本文将重点介绍如何利用CompletableFuture优化并发查询接口的响应速度。实现思路:要优化并发查询接口的响应速度,传统的优化方式是通过多线程来并行执行多个查询任务。但这种做法存在一些缺陷:1.创建和管理线程的开销较大,如果线程数量过多,会给系统带来很大的压力。2.如果查询任务的执行时间不均匀,会导致部分线程需要长时间等待,资源利用率低下。而CompletableFuture提供了一种更优雅、更高效的解决方案。其核心思路是:每个查询任务都封装为一个CompletableFuture异步任务,由线程池并行执行。通过CompletableFuture.allOf()方法等待所有异步任务完成。最后从每个任务的结果中组装出最终需要的数据对象。一创建CompetableFuture// 从一个供给函数创建 CompletableFutureString future CompletableFuture.supplyAsync(() - Hello); // 从一个运行函数创建 CompletableFutureVoid future CompletableFuture.runAsync(() - System.out.println(Hello)); // 从一个已有的结果创建 CompletableFutureString future CompletableFuture.completedFuture(Hello);二.链式调用CompletableFutureString resultFuture CompletableFuture.supplyAsync(() - Hello) .thenApply(s - s World) // 对结果进行转换 .thenCompose(s - getResult(s)); // 组合另一个异步操作三. 异常处理CompletableFutureString 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 LinkedBlockingQueueRunnable()); 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. 异步并行执行多个查询任务 CompletableFutureString baseInfoFuture CompletableFuture.supplyAsync(() - service.getBaseInfo(productId)); CompletableFutureInteger stockFuture CompletableFuture.supplyAsync(() - service.getStock(productId)); CompletableFutureDouble priceFuture CompletableFuture.supplyAsync(() - service.getPrice(productId)); CompletableFutureString 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); } }执行结果商品详情 商品ID1001名称小米14分类手机 库存1000件 价格¥3999.0 评价好评率98%累计评价10w串行执行总耗时10080120150450ms并行执行仅≈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. 第一步异步查询用户信息 CompletableFutureUser userFuture CompletableFuture.supplyAsync(() - userService.getUser(userId)); // 2. 第二步依赖用户信息异步查询专属订单thenCompose用于异步任务依赖 CompletableFutureString 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的代码风格函数式、简洁、优雅,也使得代码更加易读易维护。但是,异步编程也不是万能的,它需要开发者转变思维模式,还需要权衡利弊。在实际项目中,我们可以结合其他优化手段,选择合适的方案,以达到最佳的性能效果。