CompletableFuture 实战模板(超时、组合、异常链处理)

这篇给你可直接复用的模板,不讲大道理,讲"真实项目里怎么写才稳"。

先说结论

在项目里写 CompletableFuture,最容易出问题的就三件事:

  1. 没有超时(线程一直卡住)
  2. 异常没兜底(主线程卡死 or 悄悄失败)
  3. 线程池乱用(公共池被打爆)

解决思路:有超时、有兜底、有隔离的线程池


一、基础模板:统一线程池 + 超时 + 兜底

java 复制代码
ExecutorService bizPool = new ThreadPoolExecutor(
    16, 32, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(2000),
    new ThreadFactoryBuilder().setNameFormat("biz-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

CompletableFuture<User> future = CompletableFuture
    .supplyAsync(() -> userService.getById(uid), bizPool)
    .orTimeout(800, TimeUnit.MILLISECONDS)
    .exceptionally(ex -> {
        log.warn("userService fallback, uid={}", uid, ex);
        return User.DEFAULT;
    });

这个模板适合 80% 的"单个异步调用"。


二、并行查询聚合(allOf)

需求:并发查用户、订单、积分

java 复制代码
CompletableFuture<User> f1 = CompletableFuture.supplyAsync(() -> getUser(uid), bizPool);
CompletableFuture<Order> f2 = CompletableFuture.supplyAsync(() -> getOrder(uid), bizPool);
CompletableFuture<Point> f3 = CompletableFuture.supplyAsync(() -> getPoint(uid), bizPool);

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);

UserProfile profile = all.thenApply(v -> {
    return new UserProfile(f1.join(), f2.join(), f3.join());
}).orTimeout(1, TimeUnit.SECONDS)
  .exceptionally(ex -> UserProfile.fallback(uid))
  .join();

注意:join() 会把异常包装成 CompletionException,一定要有兜底。


三、竞速返回(anyOf)

需求:多个数据源取最快结果

java 复制代码
CompletableFuture<Result> f1 = CompletableFuture.supplyAsync(() -> queryA(key), bizPool);
CompletableFuture<Result> f2 = CompletableFuture.supplyAsync(() -> queryB(key), bizPool);

Result result = CompletableFuture.anyOf(f1, f2)
    .orTimeout(300, TimeUnit.MILLISECONDS)
    .thenApply(r -> (Result) r)
    .exceptionally(ex -> Result.empty())
    .join();

四、串行依赖(thenCompose)

需求:先查用户,再查用户的订单

java 复制代码
CompletableFuture<Order> future = CompletableFuture
    .supplyAsync(() -> getUser(uid), bizPool)
    .thenCompose(user -> CompletableFuture.supplyAsync(
        () -> getOrder(user.getId()), bizPool
    ))
    .orTimeout(800, TimeUnit.MILLISECONDS)
    .exceptionally(ex -> Order.EMPTY);

五、异常链处理(handle vs exceptionally)

java 复制代码
CompletableFuture<String> f = CompletableFuture
    .supplyAsync(() -> doWork(), bizPool)
    .handle((res, ex) -> {
        if (ex != null) {
            log.warn("work failed", ex);
            return "fallback";
        }
        return res;
    });

handle 能同时拿到结果和异常,适合统一兜底。


六、避免公共线程池被打爆

不要直接用 CompletableFuture.supplyAsync() 默认线程池,

它用的是 ForkJoinPool.commonPool,很容易被其他任务占满。

结论:业务异步一定要用自定义线程池。


七、一个可直接复用的"组合模板"

java 复制代码
CompletableFuture<UserProfile> profileFuture = CompletableFuture
    .supplyAsync(() -> getUser(uid), bizPool)
    .thenCombineAsync(
        CompletableFuture.supplyAsync(() -> getOrder(uid), bizPool),
        (user, order) -> new UserProfile(user, order),
        bizPool
    )
    .orTimeout(800, TimeUnit.MILLISECONDS)
    .exceptionally(ex -> UserProfile.fallback(uid));

最后总结

CompletableFuture 最靠谱的姿势是:

  1. 统一线程池
  2. 每段链路加超时
  3. 异常统一兜底
  4. 聚合前后都可回退

只要把这四件事做对,你的异步代码会稳很多。

相关推荐
June`4 分钟前
多线程redis项目之aof
数据库·redis·缓存
SimonKing14 分钟前
IP定位库的完美替代品:ip2region,开源、免费!
java·后端·程序员
Peter-OK14 分钟前
Redis从3.x到8.4的核心新特性深度解析与实战学习指南
数据库·redis·缓存
XiYang-DING15 分钟前
【Spring】Lombok
java·后端·spring
凤山老林16 分钟前
AI辅助编程:Copilot在Java开发中的最佳实践
java·人工智能·copilot
ew4521816 分钟前
【Java】Apache POI 终极封装:支持多表格循环、图片插入、日期格式化的Word导出工具类(兼容POI3.17+)
java·word·apache
铁打的阿秀16 分钟前
IDEA启动项目报错: 加载主类 com.seeburger.webedi.system.SystemApplication 时出现 LinkageError
java·ide·intellij-idea
Yeats_Liao17 分钟前
物联网接入层技术剖析(一):从select到epoll
java·linux·后端·物联网·struts
文青小兵18 分钟前
云计算Linux——数据库MySQL读写分离、数据库备份、恢复(十八)
linux·运维·服务器·数据库·mysql·云计算
上弦月-编程23 分钟前
Java类与对象:编程核心解密
java·开发语言·jvm