一、基础介绍
1. 定位
CompletableFuture 是 Java 8 引入的类,同时实现两个接口:
Future:原有异步任务结果获取接口(阻塞get())CompletionStage:链式异步编排接口 ,支持任务串行、并行、异常处理、回调,解决传统FutureTask只能阻塞获取结果的痛点。
2. 核心优势对比 FutureTask
- 支持非阻塞回调 :不用阻塞
get(),任务完成自动执行回调; - 链式编排 :
thenApply/thenCombine/thenRun串联、并行多个异步任务; - 统一异常处理 :
exceptionally() / handle()捕获异步任务异常; - 批量等待 :
allOf()等待全部任务、anyOf()任意一个完成就返回; - 手动完成任务 :
complete()手动设置结果、completeExceptionally()手动抛异常。
3. 两种运行模式
(1)默认线程池:ForkJoinPool.commonPool()
不带自定义线程池的方法(supplyAsync()/runAsync())默认使用公共池,IO密集型任务不推荐(会占满公共池阻塞其他任务)。
(2)自定义线程池重载
所有异步方法都提供第二个参数 Executor,生产环境必须传入自定义线程池隔离业务。
二、核心API分类
1. 创建异步任务(2种)
| 方法 | 作用 | 返回值 |
|---|---|---|
runAsync(Runnable) |
无入参、无返回值异步任务 | CompletableFuture<Void> |
supplyAsync(Supplier<T>) |
无入参、有返回值异步任务 | CompletableFuture<T> |
2. 串行执行(上一个任务结果作为下一个入参)
| 方法 | 区别 |
|---|---|
thenApply(Function) |
有返回值,同线程执行 |
thenApplyAsync(Function) |
有返回值,线程池异步执行 |
thenConsume(Consumer) |
无返回值,消费上一步结果 |
thenRun(Runnable) |
不接收上一步结果,无返回值 |
3. 并行组合多个任务
thenCombine:两个任务都完成,合并两个结果计算新值thenAcceptBoth:两个任务完成,消费两个结果,无返回runAfterBoth:两个任务完成,执行无参无返回任务applyToEither:两个任务任意一个先完成,使用最快的结果acceptEither:任意一个完成,消费最快结果
4. 批量等待多任务
CompletableFuture.allOf(cf1,cf2,cf3):全部任务完成才结束,无返回值CompletableFuture.anyOf(cf1,cf2,cf3):任意一个任务完成立刻返回,返回最先完成的结果
5. 异常处理(三种方式)
exceptionally():任务异常时提供兜底返回值,只能接收异常handle():无论正常/异常都会执行,同时拿到结果+异常(推荐)whenComplete():回调,无返回值,仅做日志、监控
6. 手动控制任务完成
complete(T result):手动正常完成,设置返回值completeExceptionally(Throwable ex):手动抛出异常cancel(boolean):取消任务
三、完整使用案例(分场景)
前置:自定义业务线程池(生产标准)
java
import java.util.concurrent.*;
public class CompletableFutureDemo {
// 业务专用线程池,禁止使用公共ForkJoin池
private static final Executor BIZ_POOL = new ThreadPoolExecutor(
10,
20,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
new ThreadPoolExecutor.CallerRunsPolicy()
);
场景1:基础创建异步任务(有返回/无返回)
java
// 1. 无返回值 runAsync
public static void testRunAsync() throws ExecutionException, InterruptedException {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("无返回任务执行,线程:" + Thread.currentThread().getName());
}, BIZ_POOL);
future.get(); // 阻塞等待完成
}
// 2. 有返回值 supplyAsync
public static void testSupplyAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行查询任务");
return "查询结果:Java多线程";
}, BIZ_POOL);
// 阻塞获取结果
String res = future.get();
System.out.println(res);
}
场景2:串行链式调用 thenApply(任务分步依赖)
第一步查用户ID → 第二步根据ID查用户名
java
public static void testThenApply() throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 第一步:获取用户ID
return 1001L;
}, BIZ_POOL).thenApplyAsync(userId -> {
// 第二步:根据ID查名称,接收上一步结果
return "用户" + userId + "_张三";
}, BIZ_POOL);
String name = future.get();
System.out.println(name); // 用户1001_张三
}
场景3:异常处理 handle / exceptionally
方式1:handle(推荐,正常/异常都进入回调)
java
public static void testHandle() throws InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
int a = 1 / 0; // 模拟异常
return "正常数据";
}, BIZ_POOL).handle((result, ex) -> {
if (ex != null) {
System.out.println("任务异常:" + ex.getMessage());
return "兜底默认值";
}
return result;
});
System.out.println(future.join()); // join不抛受检异常
}
方式2:exceptionally 仅异常进入
java
public static void testExceptionally() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
int a = 1 / 0;
return "ok";
}, BIZ_POOL).exceptionally(ex -> {
System.out.println("捕获异常:" + ex);
return "异常兜底";
});
System.out.println(future.join());
}
场景4:两个任务并行合并 thenCombine
任务A查商品信息、任务B查库存,两个都完成后组装返回
java
public static void testThenCombine() throws ExecutionException, InterruptedException {
// 任务1:查询商品名称
CompletableFuture<String> goodsCf = CompletableFuture.supplyAsync(() -> "Java并发编程实战", BIZ_POOL);
// 任务2:查询库存
CompletableFuture<Integer> stockCf = CompletableFuture.supplyAsync(() -> 99, BIZ_POOL);
// 两个任务全部完成,合并结果
CompletableFuture<String> resultCf = goodsCf.thenCombine(stockCf, (goods, stock) -> {
return "商品:" + goods + ",剩余库存:" + stock;
});
System.out.println(resultCf.get());
}
场景5:任意任务完成即返回 anyOf
并发调用多个接口,取最快返回的结果(如多服务冗余查询)
java
public static void testAnyOf() throws ExecutionException, InterruptedException {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "服务A返回数据";
}, BIZ_POOL);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "服务B返回数据";
}, BIZ_POOL);
CompletableFuture<Object> fastResult = CompletableFuture.anyOf(cf1, cf2);
System.out.println(fastResult.get()); // 输出服务B,执行更快
}
场景6:全部任务完成 allOf(多线程本地事务一致性核心场景)
你之前需求:多线程执行业务SQL,全部执行完毕统一提交/回滚
java
public static void testAllOfTx() throws InterruptedException {
// 模拟3个数据库任务,独立事务连接
CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> System.out.println("执行订单表写入"), BIZ_POOL);
CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> System.out.println("执行库存表扣减"), BIZ_POOL);
CompletableFuture<Void> task3 = CompletableFuture.runAsync(() -> {
int err = 1 / 0; // 模拟任务异常
}, BIZ_POOL);
// 收集所有任务
CompletableFuture<Void> allTask = CompletableFuture.allOf(task1, task2, task3);
try {
allTask.join();
} catch (CompletionException e) {
// 任意任务抛出异常,全局标记失败,统一回滚所有事务
System.err.println("存在任务执行失败,全部事务回滚,异常:" + e.getCause());
return;
}
// 无异常,统一提交所有事务
System.out.println("所有任务执行成功,统一提交事务");
}
场景7:手动完成 complete / completeExceptionally
不依赖异步线程,手动控制任务结果,适合回调接口封装
java
public static void testManualComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// 模拟异步回调后手动赋值
new Thread(() -> {
try {
Thread.sleep(500);
future.complete("手动设置成功结果");
// 异常场景:future.completeExceptionally(new RuntimeException("接口调用失败"));
} catch (InterruptedException e) {
future.completeExceptionally(e);
}
}).start();
String res = future.join();
System.out.println(res);
}
场景8:whenComplete 后置回调(日志、监控埋点)
无论成功失败,执行收尾逻辑,不修改返回值
java
public static void testWhenComplete() {
CompletableFuture.supplyAsync(() -> {
return "业务执行完成";
}, BIZ_POOL).whenComplete((res, ex) -> {
// 统一打印日志、记录耗时、埋点监控
if (ex != null) {
System.err.println("任务异常日志:" + ex.getMessage());
} else {
System.out.println("任务正常结束,结果:" + res);
}
}).join();
}
四、get() 和 join() 核心区别
future.get()- 抛出受检异常 :
InterruptedException, ExecutionException,必须try-catch捕获
- 抛出受检异常 :
future.join()- 只抛出运行时异常
CompletionException,无需强制捕获,代码更简洁,业务开发优先用join
- 只抛出运行时异常
五、生产环境避坑要点
-
禁止不指定线程池
无executor参数的supplyAsync/runAsync使用ForkJoinPool.commonPool,IO阻塞任务会占满公共池,导致全局异步任务卡死。 -
allOf不会自动抛出子任务异常
必须调用join()/get()才会触发异常,否则感知不到子任务报错。 -
线程池资源释放
程序关闭时手动shutdown()自定义线程池,避免线程泄漏。 -
异步任务中传递上下文(ThreadLocal失效)
异步线程会丢失主线程ThreadLocal(登录用户、请求ID),需要手动拷贝上下文到异步任务。 -
超时控制
长时间任务搭配orTimeout(time, unit)防止无限阻塞:javacf.orTimeout(3, TimeUnit.SECONDS).handle(...)
六、总结
CompletableFuture.allOf() 是单机多线程事务一致性最优工具:
- 每个任务独立数据库连接、关闭自动提交;
- 异步执行业务DML,异常自动封装进CompletionException;
allOf().join()等待全部任务执行完毕;- 捕获异常则批量回滚所有连接,无异常统一commit;
相比CountDownLatch/CyclicBarrier/Phaser代码更简洁,天然支持异步编排,无需手动维护计数器。