CompletableFuture学习
(个人学习记录)
学习CompletableFuture主要是为了异步操作。
引言:为什么需要 CompletableFuture?
在 Java 8 之前,我们处理异步任务依赖 Future 接口,但它有两个致命缺陷:
-
无法链式处理结果,必须通过 get() 阻塞获取,或手动轮询,代码臃肿且低效;
-
缺乏异常处理机制,一旦任务抛出异常,只能在 get() 时捕获,容错性差;
-
不支持多任务协同,无法便捷实现 "等待所有任务完成" 或 "任一任务完成" 的场景。
而 CompletableFuture 的出现彻底解决了这些问题 ------ 它实现了 Future 和 CompletionStage 接口,既是异步任务的结果容器,又是流程编排工具,让异步编程像同步代码一样简洁优雅。
本文将从基础用法到实战场景,带你全面掌握 CompletableFuture。
一、核心概念:CompletableFuture 是什么?
CompletableFuture 是 Java 8 引入的异步编程工具类,核心定位:
-
结果容器:存储异步任务的执行结果(成功 / 失败);
-
流程编排器:支持链式调用、多任务组合,无需手动管理线程;
-
线程池友好:默认使用 ForkJoinPool.commonPool(),也支持自定义线程池,灵活控制资源。
它的核心优势:非阻塞 + 链式调用 + 异常处理 + 多任务协同,让异步代码可读性、可维护性大幅提升。
二、基础用法:创建 CompletableFuture 实例
CompletableFuture 的创建方式分 3 类,覆盖不同场景:
1. 直接创建(已完成 / 失败的任务)
适合快速返回已知结果或模拟异常场景:
java
// 1. 已完成的任务(直接返回结果)
CompletableFuture<String> successFuture = CompletableFuture.completedFuture("操作成功");
String result = successFuture.join(); // 无需阻塞,直接获取结果
// 2. 已失败的任务(直接抛出异常)
CompletableFuture<String> failFuture = CompletableFuture.failedFuture(
new RuntimeException("网络请求超时")
);
// 后续获取结果时会触发异常
2. 异步执行无返回值任务(runAsync)
适合不需要返回结果的异步操作(如日志打印、数据同步),底层是 Runnable:
java
// 方式1:使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("异步执行无返回值任务(默认线程池)");
// 模拟耗时操作(如文件写入)
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
// 方式2:使用自定义线程池(推荐,避免默认线程池资源竞争)
ExecutorService customExecutor = Executors.newFixedThreadPool(3);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("异步执行无返回值任务(自定义线程池)");
}, customExecutor);
3. 异步执行有返回值任务(supplyAsync)
适合需要返回结果的异步操作(如数据库查询、接口调用),底层是 Supplier:
java
// 默认线程池,返回 String 类型结果
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
// 模拟耗时查询(如查询用户信息)
try { Thread.sleep(1500); } catch (InterruptedException e) {
throw new RuntimeException("任务被中断", e);
}
return "用户ID:10086";
});
// 自定义线程池(推荐生产环境使用)
CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
return 100 + 200; // 模拟计算任务,返回 300
}, customExecutor);
💡 注意:自定义线程池需手动关闭(executor.shutdown()),避免资源泄露。
三、核心技巧:链式处理异步结果
CompletableFuture 的灵魂在于 链式调用------ 一个任务完成后,自动触发下一个任务,无需阻塞等待。
所有链式方法均返回新的 CompletableFuture,支持无限级联,核心方法分 3 类:
1. 处理结果(有返回值):thenApply
接收前一个任务的结果,处理后返回新结果,适合 "结果转换" 场景:
java
// 场景:查询用户信息 → 转换为用户DTO → 加密敏感字段
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
return "原始用户信息:id=10086,name=张三"; // 第一步:查询原始数据
})
.thenApply(originInfo -> {
return originInfo.replace("原始", "转换后"); // 第二步:转换格式
})
.thenApply(dto -> {
return "加密后:" + dto.hashCode(); // 第三步:加密处理
});
// 最终结果:加密后:xxxx( hashCode 值)
2. 消费结果(无返回值):thenAccept
接收前一个任务的结果,仅消费(如打印、存储),无返回值:
java
CompletableFuture.supplyAsync(() -> "异步任务结果")
.thenAccept(result -> {
System.out.println("消费结果:" + result); // 输出:消费结果:异步任务结果
});
3. 执行后续任务(忽略结果):thenRun
不关心前一个任务的结果,仅执行后续操作(如发送通知):
java
CompletableFuture.supplyAsync(() -> {
// 模拟文件上传
return "文件URL";
})
.thenRun(() -> {
System.out.println("文件上传完成,发送通知给用户");
});
4. 异步链式:带 Async 后缀的方法
上述 thenApply/thenAccept/thenRun 默认使用前一个任务的线程或当前线程执行,若需异步执行后续任务,可使用带 Async 后缀的方法:
java
CompletableFuture.supplyAsync(() -> "hello")
// 用自定义线程池异步处理结果
.thenApplyAsync(s -> s + " world", customExecutor)
// 再用自定义线程池异步消费
.thenAcceptAsync(System.out::println, customExecutor);
💡 关键区别:
-
无 Async:后续任务在当前线程或前任务线程执行(同步执行);
-
有 Async:后续任务在指定线程池(或默认线程池)异步执行。
四、异常处理:优雅解决异步任务报错
异步任务中抛出的异常不会直接暴露,需通过专门的方法捕获,核心有 2 种方式:
1. exceptionally:捕获异常,返回默认值
类似 try-catch,仅在前面任务失败时执行,返回默认结果:
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟任务失败
throw new RuntimeException("数据库连接失败");
})
.exceptionally(ex -> {
// 捕获异常,返回默认值
System.out.println("异常原因:" + ex.getMessage());
return "默认用户信息";
});
// 最终结果:默认用户信息
2. handle:成功 / 失败都处理
无论前面任务成功还是失败,都会执行,同时获取结果和异常:
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello")
.handle((result, ex) -> {
if (ex != null) {
// 异常处理
return "处理异常:" + ex.getMessage();
} else {
// 成功处理
return result + " handle";
}
});
// 最终结果:hello handle
💡 推荐使用 handle:覆盖所有场景,容错性更强。
五、进阶用法:多任务组合
CompletableFuture 支持多个异步任务的协同,核心有 2 个方法:allOf 和 anyOf。
1. allOf:等待所有任务完成
接收多个 CompletableFuture,返回一个新的 CompletableFuture,所有任务都完成后才会完成(适合 "批量处理" 场景):
java
// 场景:批量查询3个服务的数据,全部查询完成后汇总
CompletableFuture<String> serviceA = CompletableFuture.supplyAsync(() -> "服务A数据");
CompletableFuture<String> serviceB = CompletableFuture.supplyAsync(() -> "服务B数据");
CompletableFuture<String> serviceC = CompletableFuture.supplyAsync(() -> "服务C数据");
// 等待所有任务完成
CompletableFuture<Void> allFuture = CompletableFuture.allOf(serviceA, serviceB, serviceC);
// 所有任务完成后,汇总结果
allFuture.thenRun(() -> {
try {
String a = serviceA.get();
String b = serviceB.get();
String c = serviceC.get();
System.out.println("汇总结果:" + a + "," + b + "," + c);
} catch (Exception e) {
e.printStackTrace();
}
});
⚠️ 注意:allOf 的返回结果是 Void,需通过原始 Future 的 get() 或 join() 获取各自结果。
2. anyOf:等待任一任务完成
接收多个 CompletableFuture,返回一个新的 CompletableFuture,任一任务完成后就会完成(适合 "兜底方案" 场景):
java
// 场景:查询主服务和备用服务,哪个先返回就用哪个
CompletableFuture<String> mainService = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(300); } catch (InterruptedException e) {}
return "主服务数据";
});
CompletableFuture<String> backupService = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) {}
return "备用服务数据";
});
// 等待任一任务完成
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(mainService, backupService);
// 第一个完成的任务结果
anyFuture.thenAccept(result -> {
System.out.println("使用最快返回的数据:" + result); // 输出:备用服务数据
});
六、获取结果:get () vs join ()
当需要主动获取 CompletableFuture 的结果时,有 2 个方法:
| 方法 | 异常处理方式 | 适用场景 |
|---|---|---|
| get() | 声明抛出 InterruptedException 和 ExecutionException,需显式捕获 | 需要精细处理异常的场景 |
| join() | 不声明异常,异常包装为 CompletionException(运行时异常) | 无需显式捕获,简化代码 |
示例:
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "结果");
// 使用 get():需捕获异常
try {
String result1 = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 使用 join():无需捕获,异常会直接抛出(运行时异常)
String result2 = future.join();
💡 推荐使用 join():代码更简洁,且异常可通过 exceptionally/handle 提前处理,避免显式捕获。
七、实战场景:CompletableFuture 解决实际问题
场景 1:并行查询多个接口,汇总结果
需求:查询用户的订单列表、优惠券列表、积分信息,并行执行,全部完成后返回给前端。
java
// 自定义线程池(核心线程数=3,避免资源竞争)
ExecutorService executor = Executors.newFixedThreadPool(3);
// 1. 并行查询3个接口
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> orderService.queryOrders(userId), executor);
CompletableFuture<List<Coupon>> couponFuture = CompletableFuture.supplyAsync(() -> couponService.queryCoupons(userId), executor);
CompletableFuture<Integer> pointFuture = CompletableFuture.supplyAsync(() -> pointService.queryPoints(userId), executor);
// 2. 等待所有任务完成,汇总结果
CompletableFuture<UserInfoDTO> resultFuture = CompletableFuture.allOf(orderFuture, couponFuture, pointFuture)
.handle((voidResult, ex) -> {
if (ex != null) {
throw new RuntimeException("查询用户信息失败", ex);
}
// 汇总结果
return new UserInfoDTO(
orderFuture.join(),
couponFuture.join(),
pointFuture.join()
);
});
// 3. 返回结果(Spring Boot 中可直接返回 CompletableFuture)
return resultFuture;
场景 2:异步处理文件上传,回调通知
需求:用户上传文件后,异步处理文件(转格式、压缩),处理完成后发送短信通知。
java
@PostMapping("/upload")
public CompletableFuture<String> uploadFile(MultipartFile file) {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 1. 异步处理文件(无返回值)
CompletableFuture<Void> processFuture = CompletableFuture.runAsync(() -> {
fileService.convertFormat(file); // 转格式
fileService.compress(file); // 压缩
}, executor);
// 2. 处理完成后发送通知
return processFuture.thenApplyAsync(voidResult -> {
smsService.sendNotification("文件处理完成");
return "上传成功,已通知用户";
}, executor);
}
八、注意事项与最佳实践
-
自定义线程池:避免使用默认的 ForkJoinPool.commonPool(),尤其是在高并发场景,容易导致线程资源竞争;
-
线程池关闭:手动创建的线程池需在任务完成后关闭(executor.shutdown()),或使用 try-with-resources 自动关闭;
-
避免过度异步:CPU 密集型任务不适合异步(会增加线程切换开销),仅推荐 IO 密集型任务(如网络请求、文件读写);
-
异常全覆盖:所有异步任务都需通过 exceptionally/handle 处理异常,避免 "静默失败";
-
结果获取时机:尽量通过链式方法处理结果,而非主动调用 get()/join() 阻塞线程。
总结
CompletableFuture 是 Java 异步编程的 "瑞士军刀",它让原本复杂的异步流程变得清晰、简洁:
-
无需手动管理线程,专注业务逻辑;
-
链式调用 + 异常处理,代码可读性大幅提升;
-
多任务组合(allOf/anyOf),轻松应对复杂场景。
掌握 CompletableFuture 后,可以优雅地处理 IO 密集型任务、并行计算、兜底方案等场景,让程序性能和可维护性更上一层楼。