这篇给你可直接复用的模板,不讲大道理,讲"真实项目里怎么写才稳"。
先说结论
在项目里写 CompletableFuture,最容易出问题的就三件事:
- 没有超时(线程一直卡住)
- 异常没兜底(主线程卡死 or 悄悄失败)
- 线程池乱用(公共池被打爆)
解决思路:有超时、有兜底、有隔离的线程池。
一、基础模板:统一线程池 + 超时 + 兜底
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 最靠谱的姿势是:
- 统一线程池
- 每段链路加超时
- 异常统一兜底
- 聚合前后都可回退
只要把这四件事做对,你的异步代码会稳很多。