CompletableFuture深度解析:异步编程与任务编排的实现

📅 2026/6/30 2:06:28
CompletableFuture深度解析:异步编程与任务编排的实现
这里假设每个平台获取原价格与优惠券的接口已经实现、且都是需要调用HTTP接口查询的耗时操作接口每个耗时1s左右。根据需求理解可以很自然的写出对应实现代码javapublic int getCheapestPlatAndPrice(String product){ int taoBaoPrice computeRealPrice(HttpRequestMock.getTaoBaoPrice(product), HttpRequestMock.getTaoBaoDiscounts(product)); int jingDongPrice computeRealPrice(HttpRequestMock.getJingDongPrice(product), HttpRequestMock.getJingDongDiscounts(product)); int pinDuoDuoPrice computeRealPrice(HttpRequestMock.getPinDuoDuoPrice(product), HttpRequestMock.getPinDuoDuoDiscounts(product)); // 计算并选出实际价格最低的平台 return Stream.of(taoBaoPrice, jingDongPrice, pinDuoDuoPrice).min(Comparator.comparingInt(p - p)).get(); }运行测试下ini14:58:32.330228700[main]获取淘宝上iphone16的价格完成 5199 14:58:33.351948100[main]获取淘宝上iphone16的折扣价格完成 200 14:58:33.352933400[main]计算实际价格完成 4999 14:58:34.364138900[main]获取京东上iphone16的价格完成 5299 14:58:35.377258800[main]获取京东上iphone16的折扣价格完成 150 14:58:35.378257300[main]计算实际价格完成 5149 14:58:36.392813800[main]获取拼多多上iphone16的价格完成 5399 14:58:37.405863200[main]获取拼多多上iphone16的折扣价格完成 99 14:58:37.406712600[main]计算实际价格完成 5300 4999 耗时6142ms结果符合预期功能正常但是耗时较长。试想一下假如你在某个APP操作需要等待6s才返回最终计算结果那不得直接摔手机梳理下代码的实现思路可以知道所有的环节都是串行实现的的由于每个查询接口的耗时都是1s因此每个环节耗时加到一起接口总耗时超过6s。但实际上每个平台之间的操作是互不干扰的那其实就可以通过多线程的方式同时去分别执行各个平台的逻辑处理最后将各个平台的结果汇总到一起比对得到最低价格。所以整个执行过程会变成如下的效果因此为了提升性能可以采用线程池来负责多线程的处理操作因为需要得到各个子线程处理的结果所以需要使用Future来实现javapublic Integer getCheapestPlatAndPrice2(String product) { Future Integer taoBaoFuture threadPool.submit(() - computeRealPrice(HttpRequestMock.getTaoBaoPrice(product), HttpRequestMock.getTaoBaoDiscounts(product))); Future Integer jingDongFuture threadPool.submit(() - computeRealPrice(HttpRequestMock.getJingDongPrice(product), HttpRequestMock.getJingDongDiscounts(product))); Future Integer pinDuoDuoFuture threadPool.submit(() - computeRealPrice(HttpRequestMock.getPinDuoDuoPrice(product), HttpRequestMock.getPinDuoDuoDiscounts(product))); // 等待所有线程结果都处理完成然后从结果中计算出最低价 return Stream.of(taoBaoFuture, jingDongFuture, pinDuoDuoFuture) .map(price - { try { return price.get(); } catch (Exception e) { return null; } }) .min(Comparator.comparingInt(p - p)) .get(); }上述代码中将三个不同平台对应的Callable函数逻辑放入到ThreadPool中去执行返回Future对象然后再逐个通过Future.get()接口阻塞获取各自平台的结果最后经比较处理后返回最低价信息。执行代码可以看到执行结果与过程如下ini15:19:25.793891500[pool-1-thread-3]获取拼多多上iphone16的价格完成 5399 15:19:25.793891500[pool-1-thread-2]获取京东上iphone16的价格完成 5299 15:19:25.794891500[pool-1-thread-1]获取淘宝上iphone16的价格完成 5199 15:19:26.816140300[pool-1-thread-2]获取京东上iphone16的折扣价格完成 150 15:19:26.816140300[pool-1-thread-3]获取拼多多上iphone16的折扣价格完成 99 15:19:26.816923600[pool-1-thread-3]计算实际价格完成 5300 15:19:26.816923600[pool-1-thread-2]计算实际价格完成 5149 15:19:26.817921500[pool-1-thread-1]获取淘宝上iphone16的折扣价格完成 200 15:19:26.820923400[pool-1-thread-1]计算实际价格完成 4999 4999 耗时2085ms接口总耗时从6s下降到了2s效果还是很显著的。但是是否还能再压缩一些呢基于上面按照平台拆分并行处理的思路继续推进我们可以看出每个平台内的处理逻辑其实可以分为3个主要步骤获取原始价格耗时操作获取折扣优惠耗时操作得到原始价格和折扣优惠之后计算实付价格这3个步骤中其实第1、2两个耗时操作也是相对独立的如果也能并行处理的话响应时长上应该也能继续缩短即如下的处理流程这里当然也可以继续使用上面提到的线程池Future的方式但Future在应对并行结果组合以及后续处理等方面显得力不从心弊端明显代码写起来会非常拖沓先封装Callable函数放到线程池中去执行查询操作然后分三组阻塞等待结果并计算出各自结果最后再阻塞等待价格计算完成后汇总得到最终结果。说到这里呢就需要CompletableFuture登场了CompletableFuture可以很轻松的来完成任务的并行处理以及各个并行任务结果之间的组合再处理等操作。使用CompletableFuture编写实现代码如下javapublic Integer getCheapestPlatAndPrice3(String product) { CompletableFuture Integer taoBao CompletableFuture.supplyAsync(() - HttpRequestMock.getTaoBaoPrice(product)).thenCombine(CompletableFuture.supplyAsync(() - HttpRequestMock.getTaoBaoDiscounts(product)), this::computeRealPrice); CompletableFuture Integer jingDong CompletableFuture.supplyAsync(() - HttpRequestMock.getJingDongPrice(product)).thenCombine(CompletableFuture.supplyAsync(() - HttpRequestMock.getJingDongDiscounts(product)), this::computeRealPrice); CompletableFuture Integer pinDuoDuo CompletableFuture.supplyAsync(() - HttpRequestMock.getPinDuoDuoPrice(product)).thenCombine(CompletableFuture.supplyAsync(() - HttpRequestMock.getPinDuoDuoDiscounts(product)), this::computeRealPrice); // 排序并获取最低价格 return Stream.of(taoBao, jingDong, pinDuoDuo) .map(CompletableFuture::join) .min(Comparator.comparingInt(p - p)) .get(); }看下执行结果符合预期而接口耗时则降到了1s因为依赖的每一个查询实际操作的接口耗时都是模拟的1s所以这个结果已经算是此复合接口能达到的极限值了。ini15:29:04.911516600[ForkJoinPool.commonPool-worker-1]获取淘宝上iphone16的价格完成 5199 15:29:04.911516600[ForkJoinPool.commonPool-worker-4]获取京东上iphone16的折扣价格完成 150 15:29:04.911516600[ForkJoinPool.commonPool-worker-2]获取淘宝上iphone16的折扣价格完成 200 15:29:04.911516600[ForkJoinPool.commonPool-worker-3]获取京东上iphone16的价格完成 5299 15:29:04.911516600[ForkJoinPool.commonPool-worker-5]获取拼多多上iphone16的价格完成 5399 15:29:04.911516600[ForkJoinPool.commonPool-worker-6]获取拼多多上iphone16的折扣价格完成 99 15:29:04.924568[ForkJoinPool.commonPool-worker-2]计算实际价格完成 4999 15:29:04.924568[ForkJoinPool.commonPool-worker-3]计算实际价格完成 5149 15:29:04.924568[ForkJoinPool.commonPool-worker-6]计算实际价格完成 5300 4999 耗时1071ms这里CompletableFuture执行时所使用的默认线程池是ForkJoinPool。Future与CompletableFuture首先先来理一下Future与CompletableFuture之间的关系。Future如果接触过多线程相关的概念那Future应该不会陌生早在Java5中就已经存在了。该如何理解Future呢举个生活中的例子你去咖啡店点了一杯咖啡然后服务员会给你一个订单小票。 当服务员在后台制作咖啡的