这篇给你可直接复用的模板,不讲大道理,讲“真实项目里怎么写才稳”。
先说结论
在项目里写CompletableFuture,最容易出问题的就三件事:
- 没有超时(线程一直卡住)
- 异常没兜底(主线程卡死 or 悄悄失败)
- 线程池乱用(公共池被打爆)
解决思路:有超时、有兜底、有隔离的线程池。
一、基础模板:统一线程池 + 超时 + 兜底
ExecutorServicebizPool=newThreadPoolExecutor(16,32,60,TimeUnit.SECONDS,newLinkedBlockingQueue<>(2000),newThreadFactoryBuilder().setNameFormat("biz-%d").build(),newThreadPoolExecutor.CallerRunsPolicy());CompletableFuture<User>future=CompletableFuture.supplyAsync(()->userService.getById(uid),bizPool).orTimeout(800,TimeUnit.MILLISECONDS).exceptionally(ex->{log.warn("userService fallback, uid={}",uid,ex);returnUser.DEFAULT;});这个模板适合 80% 的“单个异步调用”。
二、并行查询聚合(allOf)
需求:并发查用户、订单、积分
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);UserProfileprofile=all.thenApply(v->{returnnewUserProfile(f1.join(),f2.join(),f3.join());}).orTimeout(1,TimeUnit.SECONDS).exceptionally(ex->UserProfile.fallback(uid)).join();注意:join()会把异常包装成CompletionException,一定要有兜底。
三、竞速返回(anyOf)
需求:多个数据源取最快结果
CompletableFuture<Result>f1=CompletableFuture.supplyAsync(()->queryA(key),bizPool);CompletableFuture<Result>f2=CompletableFuture.supplyAsync(()->queryB(key),bizPool);Resultresult=CompletableFuture.anyOf(f1,f2).orTimeout(300,TimeUnit.MILLISECONDS).thenApply(r->(Result)r).exceptionally(ex->Result.empty()).join();四、串行依赖(thenCompose)
需求:先查用户,再查用户的订单
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)
CompletableFuture<String>f=CompletableFuture.supplyAsync(()->doWork(),bizPool).handle((res,ex)->{if(ex!=null){log.warn("work failed",ex);return"fallback";}returnres;});handle能同时拿到结果和异常,适合统一兜底。
六、避免公共线程池被打爆
不要直接用CompletableFuture.supplyAsync()默认线程池,
它用的是ForkJoinPool.commonPool,很容易被其他任务占满。
结论:业务异步一定要用自定义线程池。
七、一个可直接复用的“组合模板”
CompletableFuture<UserProfile>profileFuture=CompletableFuture.supplyAsync(()->getUser(uid),bizPool).thenCombineAsync(CompletableFuture.supplyAsync(()->getOrder(uid),bizPool),(user,order)->newUserProfile(user,order),bizPool).orTimeout(800,TimeUnit.MILLISECONDS).exceptionally(ex->UserProfile.fallback(uid));最后总结
写CompletableFuture最靠谱的姿势是:
- 统一线程池
- 每段链路加超时
- 异常统一兜底
- 聚合前后都可回退
只要把这四件事做对,你的异步代码会稳很多。