CompletableFuture案例

wen java案例 3

CompletableFuture高并发案例深度解析——从入门到性能调优(附完整代码)

📚 目录导读

  1. CompletableFuture是什么——异步编程的“瑞士军刀”
  2. 核心机制拆解——四大核心方法深度对比
  3. 五大实战案例(含代码与性能对比)
    • 案例1:并行调用外部API(最典型场景)
    • 案例2:第一快响应优先(竞速模式)
    • 案例3:多任务依赖编排(流水线模式)
    • 案例4:异常处理与降级策略
    • 案例5:超时控制与线程池隔离
  4. 性能调优3大陷阱(必须避开的坑)
  5. 常见问题FAQ(面试与实战高频)

CompletableFuture是什么?

它是Java 8引入的异步编程利器,基于ForkJoinPool实现,通过链式回调解决了传统Future的三大痛点:

CompletableFuture案例

  • ❌ Future无法手动完成(只能阻塞get)
  • ❌ Future无法链式组合多个异步任务
  • ❌ Future没有异常处理机制

核心价值:将复杂的回调地狱转化为声明式流水线代码,提升CPU利用率(尤其IO密集型场景提升可达3-5倍)。


核心机制拆解(必背四大方法)

方法类别 代表方法 适用场景
创建异步任务 supplyAsync/runAsync 有返回/无返回的异步执行
结果转换 thenApply/thenAccept 对结果加工或消费
组合多个异步任务 thenCombine/allOf/anyOf 并行、汇聚、竞速
异常处理 exceptionally/handle 降级与恢复

重点理解:所有then开头的方法默认使用上一个任务的线程池,若未指定则用公共ForkJoinPool。


五大实战案例(重点!)

案例1:并行调用外部API(提高3倍性能)

场景:用户详情页需要同时获取用户信息、订单列表、优惠券,三个接口互不依赖。

ExecutorService pool = Executors.newFixedThreadPool(10);
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userApi.getUser(id), pool);
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> orderApi.getOrders(id), pool);
CompletableFuture<List<Coupon>> couponFuture = CompletableFuture.supplyAsync(() -> couponApi.getCoupons(id), pool);
// 三者都完成后汇聚(allOf)
CompletableFuture.allOf(userFuture, orderFuture, couponFuture).join();
User user = userFuture.get();
List<Order> orders = orderFuture.get();
List<Coupon> coupons = couponFuture.get();

🔹 性能对比:串行需 800ms(3个接口各约300ms),并行仅需 320ms。
🔹 关键点:务必传入自定义线程池,避免默认池被阻塞。


案例2:第一快响应优先(竞速抢票)

场景:从多个服务商(阿里、腾讯、华为)获取最低价,谁先返回用谁。

CompletableFuture<Price> alibaba = CompletableFuture.supplyAsync(() -> http.getPrice("ali"), pool);
CompletableFuture<Price> tencent = CompletableFuture.supplyAsync(() -> http.getPrice("tencent"), pool);
CompletableFuture<Price> huawei = CompletableFuture.supplyAsync(() -> http.getPrice("huawei"), pool);
CompletableFuture<Object> firstResult = CompletableFuture.anyOf(alibaba, tencent, huawei);
Price bestPrice = (Price) firstResult.get();

🔹 底层原理anyOf内部通过AcceptEither实现,谁先完成触发回调。


案例3:多任务依赖编排(对账系统流水线)

场景:先查订单 → 再查支付流水 → 最后做金额匹配。

CompletableFuture.supplyAsync(() -> orderService.getOrder(orderId), pool)
    .thenApplyAsync(order -> paymentService.getPayment(order.getPayId()), pool)
    .thenApplyAsync(payment -> checkService.compare(order, payment), pool)
    .thenAccept(result -> log.info("对账结果:{}", result))
    .exceptionally(e -> { log.error("对账失败", e); return null; });

🔹 边界情况:若中间步骤需要orderpayment两个参数,用thenCombine组合两个独立Future。


案例4:异常处理与降级策略(电商库存查询)

场景:查询库存失败时,返回默认值“有货”,并记录告警。

CompletableFuture<Integer> stockFuture = CompletableFuture.supplyAsync(() -> 
    stockService.getStock(skuId), pool)
    .exceptionally(throwable -> {
        alertService.send(throwable.getMessage()); // 告警
        return 100; // 降级默认库存
    })
    .handle((result, throwable) -> {
        if (throwable != null) return 0; // 兜底
        return result;
    });

🔹 注意exceptionally只能处理上游异常,handle可处理整个链路的异常。


案例5:超时控制与线程池隔离(防止雪崩)

场景:第三方接口慢导致线程耗尽,需要5秒超时+独立线程池。

// 自定义线程池隔离
ThreadPoolExecutor ioPool = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>(20), new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build());
CompletableFuture<Result> future = CompletableFuture.supplyAsync(() -> 
    http.post(url, data), ioPool)
    .orTimeout(5, TimeUnit.SECONDS)  // 超时抛出TimeoutException
    .exceptionally(e -> Result.timeout()); // 降级

🔹 陷阱:若用get(5, TimeUnit.SECONDS)只会阻塞当前线程,而orTimeout会取消异步任务(若支持中断)。


性能调优3大陷阱(多数程序员踩过)

  1. 默认ForkJoinPool被耗尽
    ForkJoinPool.commonPool()默认线程数为CPU核数-1,在IO密集型场景下会卡死。务必自定义线程池,并设置合理队列大小。

  2. 无界队列导致OOM
    newLinkedBlockingQueue()无界,若任务积压过多会内存溢出。建议用有界队列+拒绝策略(如CallerRunsPolicy)。

  3. 滥用join()/get()阻塞
    在业务线程里如果对allOf().join(),其实相当于退化为同步。正确姿势:让最后一个异步任务接收最终结果,不阻塞主线程。


常见问题FAQ(面试高频)

Q1:CompletableFuture和FutureTask有何区别?
A:FutureTask需要手动配合线程池,且无法链式组合;CompletableFuture内建回调链和组合方法,支持依赖编排。

Q2:thenApplythenCompose的区别?
A:thenApply返回CompletableFuture<T>的扁平结果;thenCompose主要用于当回调函数本身返回CompletableFuture时,避免嵌套CompletableFuture<CompletableFuture<T>>

Q3:如何实现“任务A完成后,无论成功失败都执行任务B”?
A:使用whenComplete(只消费不返回)或handle(可返回新值),两者都会在任务完成后触发,不区分成功失败。

Q4:线程池中的异常会丢失吗?
A:不会,但需捕获,未捕获的异常会被存入Future的结果中,通过get()exceptionally抛出。


CompletableFuture的核心价值在于声明式组合异步任务,但性能调优的关键在于线程池隔离超时控制,建议每个业务模块独立线程池,并使用有界队列。异步不是银弹,对于CPU密集型任务反而可能降低性能,动手实践才能彻底掌握,建议用以上代码改造现有同步接口,感受吞吐量的变化。

抱歉,评论功能暂时关闭!