在现代 Java 开发中,异步编程已成为提升性能与响应性的关键手段。自 Java 8 起,CompletableFuture 为我们提供了一套丰富且链式的异步处理机制,让我们可以轻松编写非阻塞代码。本文将从基础使用、组合操作、错误处理和实际案例四个角度,详细剖析如何在项目中运用 CompletableFuture。
一、基础概念与核心 API
CompletableFuture 继承自 Future 并实现了 CompletionStage 接口,主要特点是:
- 可组合:通过
thenApply,thenCompose,thenCombine等方法实现多个任务的串行或并行执行。 - 可回调:
whenComplete,exceptionally等方法支持在任务完成后立即执行回调。 - 线程安全:内部使用
AtomicReference管理状态,支持多线程并发。
1.1 创建实例
// 使用 supplyAsync 创建异步任务
CompletableFuture <Integer> future = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try { Thread.sleep(500); } catch (InterruptedException e) {}
return 42;
});
如果想在自定义线程池中执行,可传入 Executor:
Executor executor = Executors.newFixedThreadPool(4);
CompletableFuture <String> future = CompletableFuture.supplyAsync(() -> {
return "Hello";
}, executor);
1.2 结果获取
get():阻塞等待,抛出ExecutionException或InterruptedException。join():类似get(),但不会包装异常,直接抛CompletionException。orTimeout/completeOnTimeout(Java 9+):支持超时返回默认值。
二、组合与并行
2.1 链式调用
CompletableFuture <String> result = CompletableFuture.supplyAsync(() -> "world")
.thenApply(String::toUpperCase)
.thenApply(s -> "Hello, " + s + "!")
.exceptionally(ex -> "Fallback");
2.2 并行执行并合并
CompletableFuture <Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture <Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture <Integer> sum = f1.thenCombine(f2, Integer::sum);
sum.thenAccept(System.out::println); // 输出 30
2.3 并行流 + CompletableFuture
List <Integer> nums = List.of(1, 2, 3, 4, 5);
CompletableFuture<List<Integer>> future = CompletableFuture.supplyAsync(() ->
nums.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList())
);
future.thenAccept(System.out::println);
三、异常处理
异常处理是异步编程的痛点。CompletableFuture 提供多种方式:
exceptionally(Function<Throwable, T> fn):在异常时提供默认值。handle(BiFunction<T, Throwable, R> fn):统一处理成功或失败。whenComplete(BiConsumer<T, Throwable> fn):无论成功失败都执行,常用于日志。
CompletableFuture <String> risky = CompletableFuture.supplyAsync(() -> {
if (new Random().nextBoolean()) throw new RuntimeException("boom");
return "ok";
});
risky.handle((res, ex) -> {
if (ex != null) return "fallback";
return res;
}).thenAccept(System.out::println);
四、实际案例:异步 Web 服务调用
假设我们在 Spring Boot 项目中需要调用两个外部 REST 接口,并行获取数据后做整合。
@Service
public class AggregationService {
private final WebClient client = WebClient.create();
public CompletableFuture <CombinedResult> fetchData() {
CompletableFuture <DataA> futureA = client.get()
.uri("/serviceA")
.retrieve()
.bodyToMono(DataA.class)
.toFuture();
CompletableFuture <DataB> futureB = client.get()
.uri("/serviceB")
.retrieve()
.bodyToMono(DataB.class)
.toFuture();
return futureA.thenCombine(futureB, (a, b) -> new CombinedResult(a, b))
.exceptionally(ex -> {
// 记录错误,返回默认数据
log.error("Aggregation failed", ex);
return new CombinedResult(defaultA, defaultB);
});
}
}
- 优点:
thenCombine让两条链路并行完成,减少整体延迟。 - 错误容忍:
exceptionally统一处理,保证业务不会因为单个接口失败而中断。
五、性能建议
-
线程池合理配置
ForkJoinPool.commonPool()用于 CPU 密集型任务。- 对 IO 密集型任务使用自定义线程池,线程数根据
ExecutorService的availableProcessors()与 IO 阻塞比例估算。
-
避免过度拆分
- 过多的小任务会导致上下文切换成本高。
- 适度使用
thenCompose合并子任务,减少CompletableFuture创建。
-
使用
asynchronous版本- 例如
CompletableFuture.supplyAsync与CompletableFuture.runAsync。 runAsync适合无返回值的任务,避免不必要的包装。
- 例如
六、总结
CompletableFuture是 Java 8 及以后异步编程的核心工具,具备链式、组合、异常处理等丰富功能。- 通过合理的线程池管理、异常统一处理和任务组合,能够显著提升系统的吞吐量与响应性。
- 在实际项目中,结合
WebClient、ExecutorService等框架使用,可构建高性能、易维护的异步服务。
掌握这些技巧后,你就能在 Java 项目中自如使用异步编程,为系统带来更高的并发性能与更好的用户体验。

发表回复