【JUC】CompletableFuture
CompletableFuture是 Java 8 引入的一个非常强大的异步编程工具它解决了传统Future的几个核心痛点无法回调、无法组合、无法手动完成。下面我从设计理念到实战用法系统讲一下。一、为什么需要它先看 Future 的局限ExecutorServiceexecutorExecutors.newFixedThreadPool(10);FutureStringfutureexecutor.submit(()-{Thread.sleep(1000);return结果;});// 问题1get() 是阻塞的必须等结果Stringresultfuture.get();// 问题2无法在任务完成后自动执行下一步// 只能自己轮询 isDone()非常笨拙// 问题3多个 Future 无法方便地组合FutureStringf1...;FutureStringf2...;// 想让 f1 和 f2 都完成后合并结果只能自己写阻塞逻辑CompletableFuture实现了Future和CompletionStage两个接口前者负责取结果后者负责链式编排。二、创建四种常用方式// 1. 直接返回已知结果CompletableFutureStringf1CompletableFuture.completedFuture(hello);// 2. 无返回值的异步任务返回 CompletableFutureVoidCompletableFutureVoidf2CompletableFuture.runAsync(()-{System.out.println(跑一个没返回值的任务);});// 3. 有返回值的异步任务CompletableFutureStringf3CompletableFuture.supplyAsync(()-{return有返回值;});// 4. 指定线程池强烈推荐见后文ExecutorServicepoolExecutors.newFixedThreadPool(4);CompletableFutureStringf4CompletableFuture.supplyAsync(()-任务,pool);⚠️ 不传线程池时默认使用ForkJoinPool.commonPool()。这个公共池的线程数是CPU核心数 - 1一旦被阻塞型任务占满会影响整个 JVM 中所有使用它的代码。生产环境务必传自定义线程池。三、核心链式编排CompletionStage 的方法这是CompletableFuture的灵魂。任务完成后可以自动触发下一步全程非阻塞。1. 转换结果thenApply / thenApplyAsyncCompletableFutureStringfutureCompletableFuture.supplyAsync(()-hello).thenApply(s-s world)// 拿到上一步结果做转换.thenApply(String::toUpperCase);System.out.println(future.join());// HELLO WORLDthenApply用当前线程或完成上一步的线程执行thenApplyAsync提交到线程池异步执行2. 消费结果thenAccept / thenRunCompletableFuture.supplyAsync(()-结果).thenAccept(r-System.out.println(收到r));// 有入参无返回CompletableFuture.supplyAsync(()-结果).thenRun(()-System.out.println(不关心上一步结果));// 无入参无返回3. 组合两个任务thenCompose vs thenCombine这两个很容易混淆重点区分// thenCompose串行依赖第二个任务依赖第一个的结果// 相当于 flatMap避免嵌套 CompletableFutureCompletableFutureTCompletableFutureStringfgetUserById(1).thenCompose(user-getOrdersByUser(user));// 返回的是 CompletableFuture// thenCombine并行独立两个任务都完成后合并CompletableFutureStringf1CompletableFuture.supplyAsync(()-价格);CompletableFutureIntegerf2CompletableFuture.supplyAsync(()-100);CompletableFutureStringcombinedf1.thenCombine(f2,(p,n)-p:n);记忆口诀thenCompose是接着做依赖thenCombine是一起合并行。四、多任务编排allOf / anyOf这是实际业务里最常用的场景。allOf等所有任务完成CompletableFutureStringf1CompletableFuture.supplyAsync(()-A);CompletableFutureStringf2CompletableFuture.supplyAsync(()-B);CompletableFutureStringf3CompletableFuture.supplyAsync(()-C);CompletableFutureVoidallCompletableFuture.allOf(f1,f2,f3);// 注意allOf 返回 Void需要自己取各个结果CompletableFutureListStringresultall.thenApply(v-Stream.of(f1,f2,f3).map(CompletableFuture::join)// 此时已完成join 不阻塞.collect(Collectors.toList()));anyOf任一完成即可CompletableFutureObjectanyCompletableFuture.anyOf(f1,f2,f3);// 返回最先完成的那个任务的结果典型用途调用多个镜像源谁先返回用谁。五、异常处理非常重要异步链路里异常不会自动抛出必须显式处理否则会被静默吞掉。CompletableFuture.supplyAsync(()-{if(true)thrownewRuntimeException(出错了);returnok;})// 方式1exceptionally —— 相当于 catch可返回兜底值.exceptionally(ex-{System.out.println(捕获ex.getMessage());return默认值;})// 方式2handle —— 无论成功失败都执行能同时拿到结果和异常.handle((res,ex)-{if(ex!null)return兜底;returnres;})// 方式3whenComplete —— 类似 finally不改变结果.whenComplete((res,ex)-{System.out.println(清理工作);});顺序很关键exceptionally/handle只能捕获它前面链路抛出的异常。六、获取结果join vs getfuture.join();// 不抛受检异常抛 CompletionException运行时future.get();// 抛受检异常 InterruptedException / ExecutionException在allOf之后取值时因为任务已完成用join()更清爽。七、一个完整的实战例子假设要并行查询用户信息、订单、积分然后聚合ExecutorServicepoolExecutors.newFixedThreadPool(10);CompletableFutureUseruserFutureCompletableFuture.supplyAsync(()-userService.getUser(uid),pool);CompletableFutureListOrderorderFutureCompletableFuture.supplyAsync(()-orderService.getOrders(uid),pool);CompletableFutureIntegerpointFutureCompletableFuture.supplyAsync(()-pointService.getPoints(uid),pool);// 等三个都完成聚合成一个对象CompletableFutureUserDetaildetailFutureuserFuture.thenCombine(orderFuture,(user,orders)-newUserDetail(user,orders)).thenCombine(pointFuture,(detail,points)-{detail.setPoints(points);returndetail;}).exceptionally(ex-{log.error(聚合失败,ex);returnUserDetail.empty();});UserDetaildetaildetailFuture.join();对比串行执行总耗时从t1t2t3降到max(t1,t2,t3)。八、常见坑与最佳实践默认线程池陷阱不传Executor会用ForkJoinPool.commonPool()阻塞任务会拖垮全局。永远传自定义线程池。异常被吞链路上没有exceptionally/handle异常会静默丢失最后join时才抛出甚至被忽略。每条链路都要有兜底。thenApplyvsthenApplyAsync前者可能在上一个任务的线程里执行如果做耗时操作会阻塞那个线程后者会切到线程池。耗时操作一律用 Async 版本。allOf后取结果返回Void要自己遍历join别指望它给你 List。不要在thenApply里做阻塞 IO会占用线程应该用thenApplyAsync或直接放进supplyAsync。超时控制Java 9 可用orTimeout()和completeOnTimeout()future.orTimeout(3,TimeUnit.SECONDS);// 超时抛异常future.completeOnTimeout(默认值,3,TimeUnit.SECONDS);// 超时给默认值九、一张图理解方法分类类别方法说明转换thenApply结果 → 新结果消费thenAccept/thenRun只消费无返回串行组合thenCompose依赖上一步结果并行组合thenCombine两个独立任务合并多任务allOf/anyOf全部 / 任一异常exceptionally/handle/whenComplete兜底处理获取join/get阻塞取值总结CompletableFuture的核心价值在于用声明式的方式编排异步流程把任务A完成后做BA和C都完成后合并这类逻辑写成链式调用而不是手写回调地狱或阻塞等待。掌握好thenCompose/thenCombine的区别、allOf的用法和异常处理就能覆盖绝大多数并发场景。