CompletableFuture:异步编程的优雅艺术🎨

68 阅读7分钟

从回调地狱到链式优雅,CompletableFuture让异步编程像写诗一样!

一、开场:异步编程的痛点😫

远古时代:回调地狱

// 场景:下单 → 扣库存 → 扣积分 → 发短信
public void placeOrder(Order order) {
    new Thread(() -> {
        // 第一步:扣库存
        deductStock(order, (stockResult) -> {
            if (stockResult.isSuccess()) {
                // 第二步:扣积分
                new Thread(() -> {
                    deductPoints(order, (pointsResult) -> {
                        if (pointsResult.isSuccess()) {
                            // 第三步:发短信
                            new Thread(() -> {
                                sendSMS(order, (smsResult) -> {
                                    // 终于结束了!😭
                                    System.out.println("订单完成");
                                });
                            }).start();
                        }
                    });
                }).start();
            }
        });
    }).start();
}

问题:

  • 嵌套太深,像俄罗斯套娃🪆
  • 异常处理困难
  • 代码难以维护

现代解决方案:CompletableFuture

public CompletableFuture<Void> placeOrder(Order order) {
    return CompletableFuture
        .supplyAsync(() -> deductStock(order))      // 扣库存
        .thenCompose(stock -> deductPoints(order))  // 扣积分
        .thenCompose(points -> sendSMS(order))      // 发短信
        .thenAccept(sms -> System.out.println("订单完成"))
        .exceptionally(ex -> {
            log.error("订单失败", ex);
            return null;
        });
}

优势:

  • 链式调用,清晰流畅✨
  • 统一的异常处理
  • 代码优雅易读

二、核心概念:Future的进化史📜

阶段1:Future(JDK 5)

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Hello";
});

String result = future.get(); // ❌ 阻塞等待

问题:

  • get()会阻塞
  • 无法链式调用
  • 没有异常处理机制
  • 无法组合多个Future

阶段2:CompletableFuture(JDK 8)⭐

CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenApply(String::toUpperCase);

future.thenAccept(System.out::println); // 非阻塞

新特性:

  • ✅ 支持链式调用
  • ✅ 非阻塞回调
  • ✅ 异常处理
  • ✅ 多任务组合

三、创建CompletableFuture的4种方式🎯

方式1:runAsync(无返回值)

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("执行异步任务");
    // 无返回值
});

生活类比: 叫外卖🍕,你不关心谁送的,只要送到就行。

方式2:supplyAsync(有返回值)

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 执行耗时操作
    return "结果数据";
});

生活类比: 网购📦,你要收到商品(返回值)。

方式3:completedFuture(已完成)

CompletableFuture<String> future = CompletableFuture.completedFuture("立即返回");

生活类比: 从缓存中取数据,秒返!

方式4:手动创建并完成

CompletableFuture<String> future = new CompletableFuture<>();

// 在某个时刻完成
new Thread(() -> {
    try {
        Thread.sleep(1000);
        future.complete("手动完成"); // 正常完成
        // 或 future.completeExceptionally(new Exception()); // 异常完成
    } catch (Exception e) {
        future.completeExceptionally(e);
    }
}).start();

四、链式调用:12大操作符🔗

1. thenApply - 转换结果

CompletableFuture<Integer> future = CompletableFuture
    .supplyAsync(() -> "123")
    .thenApply(Integer::parseInt)  // String → Integer
    .thenApply(num -> num * 2);    // 123 → 246

类比: 流水线加工🏭,每一步都改变产品形态。

2. thenAccept - 消费结果(无返回)

CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(result -> System.out.println(result)); // 打印,不返回

类比: 快递到了签收📝,不需要回执。

3. thenRun - 执行后续操作(不接收结果)

CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("任务完成")); // 不关心上一步结果

类比: 做完作业就去玩,作业内容不重要。

4. thenCompose - 扁平化嵌套Future

CompletableFuture<User> future = CompletableFuture
    .supplyAsync(() -> getUserId())
    .thenCompose(userId -> CompletableFuture.supplyAsync(() -> queryUser(userId)));

类比: 先查ID,再根据ID查详情,避免Future嵌套。

5. thenCombine - 合并两个Future

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);

CompletableFuture<Integer> result = future1.thenCombine(future2, (a, b) -> a + b);
// 结果:30

类比: 你和朋友分别点外卖,都到了一起吃🍜🍕。

6. thenAcceptBoth - 消费两个结果

future1.thenAcceptBoth(future2, (result1, result2) -> {
    System.out.println(result1 + result2);
});

7. runAfterBoth - 两个都完成后执行

future1.runAfterBoth(future2, () -> {
    System.out.println("两个任务都完成了");
});

8. applyToEither - 谁快用谁

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    sleep(100);
    return "淘宝";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    sleep(200);
    return "京东";
});

CompletableFuture<String> result = future1.applyToEither(future2, s -> s);
// 结果:淘宝(更快)

类比: 同时叫两辆出租车🚕🚕,谁先到坐谁的。

9. acceptEither - 消费最快的结果

future1.acceptEither(future2, result -> {
    System.out.println("最快的结果:" + result);
});

10. runAfterEither - 任一完成就执行

future1.runAfterEither(future2, () -> {
    System.out.println("有一个完成了");
});

11. exceptionally - 异常处理

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("出错了");
    }
    return "成功";
}).exceptionally(ex -> {
    log.error("异常", ex);
    return "默认值"; // 返回降级值
});

类比: 备胎方案,挂了就用Plan B。

12. handle - 统一处理结果和异常

CompletableFuture.supplyAsync(() -> "Hello")
    .handle((result, ex) -> {
        if (ex != null) {
            return "出错了: " + ex.getMessage();
        }
        return result.toUpperCase();
    });

五、组合多个Future:allOf & anyOf🎭

allOf - 等待所有任务完成

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "任务1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "任务2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "任务3");

CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);

allFuture.thenRun(() -> {
    // 所有任务都完成了
    System.out.println("全部完成");
});

实战:并行查询多个数据源

public List<Product> searchProducts(String keyword) {
    CompletableFuture<List<Product>> taobao = 
        CompletableFuture.supplyAsync(() -> searchTaobao(keyword));
    
    CompletableFuture<List<Product>> jd = 
        CompletableFuture.supplyAsync(() -> searchJD(keyword));
    
    CompletableFuture<List<Product>> pdd = 
        CompletableFuture.supplyAsync(() -> searchPDD(keyword));
    
    // 等待所有完成
    CompletableFuture.allOf(taobao, jd, pdd).join();
    
    // 合并结果
    List<Product> result = new ArrayList<>();
    result.addAll(taobao.join());
    result.addAll(jd.join());
    result.addAll(pdd.join());
    
    return result;
}

anyOf - 任一完成即返回

CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);

anyFuture.thenAccept(result -> {
    System.out.println("最快的结果:" + result);
});

实战:多服务容灾

public String getUserInfo(Long userId) {
    CompletableFuture<String> master = 
        CompletableFuture.supplyAsync(() -> queryFromMaster(userId));
    
    CompletableFuture<String> slave1 = 
        CompletableFuture.supplyAsync(() -> queryFromSlave1(userId));
    
    CompletableFuture<String> slave2 = 
        CompletableFuture.supplyAsync(() -> queryFromSlave2(userId));
    
    // 谁快用谁
    return (String) CompletableFuture.anyOf(master, slave1, slave2).join();
}

六、线程池选择:别踩坑!⚠️

默认线程池(不推荐)

CompletableFuture.supplyAsync(() -> {
    // 使用ForkJoinPool.commonPool()
    return "Hello";
});

问题:

  • 所有异步任务共享一个线程池
  • 线程数 = CPU核心数
  • IO密集任务会阻塞其他任务

自定义线程池(推荐)✅

// 创建专用线程池
ExecutorService executor = new ThreadPoolExecutor(
    10,                      // 核心线程数
    50,                      // 最大线程数
    60L, TimeUnit.SECONDS,   // 空闲存活时间
    new LinkedBlockingQueue<>(1000),
    new ThreadFactoryBuilder().setNameFormat("async-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

// 使用自定义线程池
CompletableFuture.supplyAsync(() -> {
    return queryDatabase();
}, executor); // 指定线程池

最佳实践:

  • CPU密集型: 线程数 = CPU核心数 + 1
  • IO密集型: 线程数 = CPU核心数 * 2(或更多)
  • 不同类型任务用不同线程池

七、实战案例:电商商品详情页💰

@Service
public class ProductDetailService {
    
    @Autowired
    private ExecutorService asyncExecutor;
    
    /**
     * 查询商品详情(并行查询多个数据)
     */
    public ProductDetailVO getProductDetail(Long productId) {
        
        // 1. 查询基本信息
        CompletableFuture<Product> productFuture = CompletableFuture
            .supplyAsync(() -> productMapper.selectById(productId), asyncExecutor);
        
        // 2. 查询库存
        CompletableFuture<Stock> stockFuture = CompletableFuture
            .supplyAsync(() -> stockService.getStock(productId), asyncExecutor);
        
        // 3. 查询价格
        CompletableFuture<Price> priceFuture = CompletableFuture
            .supplyAsync(() -> priceService.getPrice(productId), asyncExecutor);
        
        // 4. 查询评论摘要
        CompletableFuture<CommentSummary> commentFuture = CompletableFuture
            .supplyAsync(() -> commentService.getSummary(productId), asyncExecutor);
        
        // 5. 查询推荐商品
        CompletableFuture<List<Product>> recommendFuture = CompletableFuture
            .supplyAsync(() -> recommendService.getRecommend(productId), asyncExecutor);
        
        // 6. 等待所有任务完成
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(
            productFuture, stockFuture, priceFuture, commentFuture, recommendFuture
        );
        
        // 7. 组装结果
        return allFuture.thenApply(v -> {
            ProductDetailVO vo = new ProductDetailVO();
            vo.setProduct(productFuture.join());
            vo.setStock(stockFuture.join());
            vo.setPrice(priceFuture.join());
            vo.setCommentSummary(commentFuture.join());
            vo.setRecommendProducts(recommendFuture.join());
            return vo;
        }).exceptionally(ex -> {
            log.error("查询商品详情失败", ex);
            return getDefaultDetail(productId); // 降级方案
        }).join();
    }
}

性能对比:

方式耗时
串行查询500ms + 200ms + 100ms + 300ms + 400ms = 1500ms
CompletableFuture并行max(500, 200, 100, 300, 400) = 500ms

**性能提升:3倍!**🚀


八、异常处理的3种姿势🛡️

方式1:exceptionally(推荐)

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) throw new RuntimeException("失败");
    return "成功";
}).exceptionally(ex -> {
    log.error("异常", ex);
    return "默认值";
});

方式2:handle(更灵活)

CompletableFuture.supplyAsync(() -> "Hello")
    .handle((result, ex) -> {
        if (ex != null) {
            return "出错: " + ex.getMessage();
        }
        return result.toUpperCase();
    });

方式3:whenComplete(不改变结果)

CompletableFuture.supplyAsync(() -> "Hello")
    .whenComplete((result, ex) -> {
        if (ex != null) {
            log.error("异常", ex);
        } else {
            log.info("成功:{}", result);
        }
        // 不能改变结果,只能记录日志
    });

九、常见陷阱与最佳实践⚠️

陷阱1:忘记指定线程池

// ❌ 使用默认ForkJoinPool
CompletableFuture.supplyAsync(() -> blockingIO());

// ✅ 指定专用线程池
CompletableFuture.supplyAsync(() -> blockingIO(), ioExecutor);

陷阱2:阻塞等待join()

// ❌ 在主线程阻塞
String result = CompletableFuture.supplyAsync(() -> "Hello").join();

// ✅ 用回调处理结果
CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(result -> handleResult(result));

陷阱3:忘记异常处理

// ❌ 异常被吞掉
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("错误");
});

// ✅ 添加异常处理
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("错误");
}).exceptionally(ex -> {
    log.error("异常", ex);
    return null;
});

陷阱4:链式调用中断

// ❌ thenApply返回null会中断
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> null) // 中断了!
    .thenAccept(System.out::println); // 不会执行

// ✅ 避免返回null
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s == null ? "" : s)
    .thenAccept(System.out::println);

十、超时控制⏰

try {
    String result = CompletableFuture
        .supplyAsync(() -> slowMethod())
        .orTimeout(3, TimeUnit.SECONDS) // JDK 9+
        .get();
} catch (TimeoutException e) {
    System.out.println("超时了");
}

// 或者使用completeOnTimeout提供默认值
String result = CompletableFuture
    .supplyAsync(() -> slowMethod())
    .completeOnTimeout("默认值", 3, TimeUnit.SECONDS) // JDK 9+
    .join();

十一、总结:CompletableFuture最佳实践📝

✅ DO

  1. 自定义线程池,避免用默认的
  2. 异常处理,用exceptionally或handle
  3. 非阻塞回调,避免用join()/get()阻塞
  4. 合理组合,用allOf/anyOf提升性能
  5. 设置超时,防止永久等待

❌ DON'T

  1. ❌ 不要在CompletableFuture中阻塞
  2. ❌ 不要忘记异常处理
  3. ❌ 不要过度使用,简单场景用Stream
  4. ❌ 不要忽略线程池配置
  5. ❌ 不要在回调中抛出异常而不处理

十二、面试高频问答💯

Q1: CompletableFuture和Future的区别?

A:

  • Future只能get()阻塞等待
  • CompletableFuture支持链式调用、回调、组合
  • CompletableFuture可以手动完成

Q2: thenApply和thenCompose的区别?

A:

  • thenApply:转换值,返回CompletableFuture<U>
  • thenCompose:扁平化,避免CompletableFuture<CompletableFuture<U>>嵌套

Q3: 如何取消一个CompletableFuture?

A: 调用cancel(true),但无法中断正在执行的任务,只能设置为取消状态。

Q4: CompletableFuture的性能如何?

A: 轻量级,性能接近原生线程池,但要注意线程池配置和避免阻塞。


下期预告: Disruptor如何做到比JDK队列快10倍?无锁的秘密!🔐