ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java异步开发 CompletableFuture,解决接口耗时过长问题

Java异步开发 CompletableFuture,解决接口耗时过长问题 做后端开发基本都会碰到接口慢的问题。我之前接手过一个老项目详情接口里面要查用户信息、查订单统计、调用第三方获取会员权益、读取配置数据。所有逻辑串行执行每一步都要等待上一步完成。第三方网络稍微抖动一下整个接口直接干到 2‑3 秒前端频繁报超时用户投诉不少。一开始想简单开线程处理老式的Future有个很麻烦的问题get () 会阻塞等待结果没办法灵活编排多个异步任务回调写起来嵌套一堆代码可读性惨不忍睹。Java8 引入的CompletableFuture补齐了这块短板可以实现任务并行、任务依赖、异常捕获、多任务聚合是现在 Java 异步开发主流方案。很多同学网上复制 demo 直接上线忽略自定义线程池、异常处理线上出现线程耗尽、吞掉异常等隐蔽 bug。下面结合真实业务场景从串行痛点到基础使用、任务编排再到生产踩坑完整过一遍。串行耗时的原始代码模拟商品详情接口需要并行获取 3 份不互相依赖的数据商品基础信息、用户会员信息、商品推荐列表。原始串行写法如下import java.util.concurrent.TimeUnit; public class SyncDemo { // 模拟查询商品信息耗时500ms public String getGoodsInfo() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(500); return 商品基础信息手机价格3999; } // 模拟查询会员信息耗时400ms public String getMemberInfo() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(400); return 会员等级VIP3折扣0.9; } // 模拟获取推荐列表耗时600ms public String getRecommendList() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(600); return 推荐商品耳机、充电器; } public static void main(String[] args) throws InterruptedException { SyncDemo demo new SyncDemo(); long start System.currentTimeMillis(); // 串行执行 String goods demo.getGoodsInfo(); String member demo.getMemberInfo(); String recommend demo.getRecommendList(); long cost System.currentTimeMillis() - start; System.out.println(goods); System.out.println(member); System.out.println(recommend); System.out.println(串行总耗时 cost ms); } }三段逻辑互不依赖串行执行总耗时大概 500400600 1500ms。三个任务完全可以并行跑理论耗时只需要最慢的 600msCompletableFuture 就可以实现这个效果。CompletableFuture 并行改造注意生产环境不要直接用默认 ForkJoinPool建议自定义业务线程池避免业务线程和框架线程互相抢占资源。import java.util.concurrent.*; public class CompletableDemo { // 自定义业务线程池项目中建议放到Spring容器管理 private static final ExecutorService businessPool Executors.newFixedThreadPool(10); public String getGoodsInfo() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(500); return 商品基础信息手机价格3999; } public String getMemberInfo() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(400); return 会员等级VIP3折扣0.9; } public String getRecommendList() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(600); return 推荐商品耳机、充电器; } public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableDemo demo new CompletableDemo(); long start System.currentTimeMillis(); // 提交异步任务传入自定义线程池 CompletableFutureString goodsFuture CompletableFuture.supplyAsync(() - { try { return demo.getGoodsInfo(); } catch (InterruptedException e) { throw new CompletionException(e); } }, businessPool); CompletableFutureString memberFuture CompletableFuture.supplyAsync(() - { try { return demo.getMemberInfo(); } catch (InterruptedException e) { throw new CompletionException(e); } }, businessPool); CompletableFutureString recommendFuture CompletableFuture.supplyAsync(() - { try { return demo.getRecommendList(); } catch (InterruptedException e) { throw new CompletionException(e); } }, businessPool); // 等待全部任务完成 CompletableFutureVoid all CompletableFuture.allOf(goodsFuture, memberFuture, recommendFuture); all.get(); String goods goodsFuture.get(); String member memberFuture.get(); String recommend recommendFuture.get(); long cost System.currentTimeMillis() - start; System.out.println(goods); System.out.println(member); System.out.println(recommend); System.out.println(并行总耗时 cost ms); businessPool.shutdown(); } }改造之后总耗时取决于执行最慢的任务接口响应速度直接提升一大截。常用任务编排 API实际开发不会只有简单并行还有任务依赖、只要任意一个任务完成就返回的场景。thenApply上一步结果作为下一步入参任务串行依赖CompletableFutureString future CompletableFuture.supplyAsync(() - 原始商品ID:1001) .thenApply(s - s 补充商品详情数据); System.out.println(future.join());anyOf多个任务任意一个完成就结束适合多渠道查询哪个先返回就用哪个结果。CompletableFutureObject any CompletableFuture.anyOf(goodsFuture, memberFuture); Object res any.join();线上高频踩坑很多人在这里翻车坑 1异常不处理直接静默丢失异常如果异步任务抛出异常不做处理调用get()/join()才会抛出。如果忘记获取结果异常直接吞掉日志看不到报错问题很难排查。 推荐使用exceptionally捕获异常设置降级返回值。CompletableFutureString f CompletableFuture.supplyAsync(() - { int a 1 / 0; return ok; }).exceptionally(ex - { // 打印异常日志业务降级返回 ex.printStackTrace(); return 获取数据失败降级结果; }); System.out.println(f.join());坑 2乱用默认线程池 ForkJoinPoolsupplyAsync不传第二个线程池参数会使用 ForkJoinPool.commonPool。这个线程池是 JVM 全局共享一旦业务任务阻塞会影响整个应用其他组件生产一定要传入自定义线程池。坑 3get () 不设置超时线程无限阻塞get () 没有超时时间如果异步任务卡死主线程会一直阻塞接口线程耗尽。一定要加超时时间。// 最多等待2秒超时直接抛出异常 String result future.get(2, TimeUnit.SECONDS);坑 4lambda 里面不要直接修改外部非线程安全变量异步多线程场景修改外部 List、Map会出现并发修改异常尽量把结果交给 CompletableFuture 收集。什么时候适合用 CompletableFuture✅适合多个互相独立的 IO 操作调用第三方接口、多表查询并行缩短接口 RT。 ❌不适合CPU 密集计算大量同步数据库事务任务之间强依赖串行逻辑强行异步只会增加代码复杂度。很多新手拿到就到处用把简单逻辑强行异步代码变复杂调试困难。异步是用来解决 IO 等待的不是万能性能神器。最后总结CompletableFuture 把 Java 异步编程门槛降下来解决老版 Future 阻塞、回调地狱问题。但异步开发要关注三件事自定义线程池、异常捕获、设置超时时间。写业务的时候优先识别接口内部哪些逻辑可以并行把互不依赖的 IO 操作交给 CompletableFuture 处理接口耗时就能明显降下来。同时也要把控度不要为了炫技到处写异步可读性永远是代码很重要的一环。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表