Java 9 改进的 CompletableFuture API:让异步编程更优雅
在 Java 8 中引入的 CompletableFuture 为异步编程带来了革命性的变化。它让开发者能够以链式调用的方式处理异步任务,避免了传统回调地狱的困扰。然而,Java 9 的发布对 CompletableFuture API 进行了多项实用且深远的改进,使得异步编程变得更加简洁、灵活和可读。
如果你正在使用 Java 8 或更早版本,那么 Java 9 的这些改进将显著提升你的开发体验。即使你是初学者,也不必担心——接下来的内容会循序渐进地带你理解这些变化背后的逻辑与价值。
CompletableFuture 的基础回顾:从 Java 8 开始
在深入 Java 9 的改进之前,先快速回顾一下 CompletableFuture 的核心概念。
CompletableFuture 是一个实现了 Future 接口的类,它代表一个异步计算的结果。你可以将它看作是一个“承诺”——我承诺在某个时间点返回结果,但不阻塞当前线程。
举个例子:假设你要从网络请求获取用户信息,同时还要从数据库查询用户的订单记录。这两个操作可以并行执行,而不需要等待一个完成才开始另一个。
// 创建一个异步任务,模拟耗时操作
CompletableFuture<String> userInfoFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("正在获取用户信息,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟网络延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "张三";
});
// 另一个异步任务
CompletableFuture<String> orderInfoFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("正在获取订单信息,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "订单 1001";
});
// 合并两个任务的结果
CompletableFuture<String> combinedFuture = userInfoFuture.thenCombine(orderInfoFuture, (user, order) -> {
return "用户:" + user + ",订单:" + order;
});
// 获取最终结果
System.out.println("最终结果:" + combinedFuture.get());
说明:上面的代码展示了如何使用
supplyAsync创建异步任务,thenCombine将两个 Future 的结果合并。整个流程是非阻塞的,主线程可以继续执行其他逻辑。
但你有没有发现一个问题?当两个任务都完成时,我们才调用 get() 获取结果。如果其中一个失败了,整个链就会中断。这正是 Java 9 想要解决的关键痛点之一。
Java 9 新增方法:handle 和 whenComplete 的增强
Java 9 为 CompletableFuture 增加了两个非常实用的方法:handle 和 whenComplete 的增强版本。它们让你能更精细地控制异常处理和结果传播。
handle 方法:统一处理成功与异常
在 Java 8 中,exceptionally 用于处理异常,但不能处理正常结果。而 handle 方法则统一处理两种情况——无论是成功还是失败。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("模拟网络异常");
}
return "数据获取成功";
});
// 使用 handle 统一处理结果和异常
future.handle((result, exception) -> {
if (exception != null) {
// 异常情况:打印错误日志,并返回默认值
System.err.println("发生异常:" + exception.getMessage());
return "默认数据";
} else {
// 成功情况:正常返回结果
System.out.println("获取到数据:" + result);
return result;
}
}).thenAccept(System.out::println);
说明:
handle接收两个参数:result是任务返回的值,exception是可能抛出的异常。当任务失败时,result为 null,exception非空。这样我们就可以在同一个回调中处理所有场景,避免了嵌套exceptionally的复杂结构。
whenComplete:执行清理或日志操作
whenComplete 用于在任务完成后执行某些操作,比如记录日志、释放资源等。它不会影响原任务的结果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行耗时任务...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "任务完成";
});
// 无论成功或失败,都会执行这个回调
future.whenComplete((result, exception) -> {
if (exception != null) {
System.err.println("任务失败,原因:" + exception.getMessage());
} else {
System.out.println("任务成功,结果:" + result);
}
// 可以在这里释放资源、关闭连接等
System.out.println("清理工作完成");
});
// 主线程继续执行
System.out.println("主线程继续...");
说明:
whenComplete不会改变原始结果,也不会阻塞后续链式调用。它非常适合用于日志记录、监控、资源释放等“副作用”操作。
支持自定义执行器:更灵活的线程管理
在 Java 8 中,supplyAsync 有两个重载版本:一个使用默认的 ForkJoinPool,另一个允许传入自定义的 Executor。但 Java 9 进一步增强了这一能力,让开发者能更灵活地控制异步任务的执行环境。
使用自定义线程池执行异步任务
// 创建一个自定义线程池,包含 4 个线程
ExecutorService executor = Executors.newFixedThreadPool(4);
// 使用自定义执行器执行异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("任务在自定义线程池中执行,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "自定义线程池任务完成";
}, executor);
// 等待结果
System.out.println("结果:" + future.get());
// 关闭线程池
executor.shutdown();
说明:通过传入自定义的
ExecutorService,你可以精确控制并发数量、线程命名、拒绝策略等。这对于高并发系统尤为重要,避免使用默认的 ForkJoinPool 导致资源争用。
静态方法:allOf 和 anyOf 的增强
Java 8 提供了 allOf 和 anyOf 用于组合多个 CompletableFuture。Java 9 对它们进行了增强,使返回类型更清晰,并支持更复杂的场景。
allOf:等待所有任务完成
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1 开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "任务1 完成";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务2 开始");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "任务2 完成";
});
// 等待所有任务完成
CompletableFuture<Void> allFuture = CompletableFuture.allOf(task1, task2);
// 获取所有结果
allFuture.thenRun(() -> {
System.out.println("所有任务已完成");
System.out.println("任务1 结果:" + task1.get());
System.out.println("任务2 结果:" + task2.get());
}).join();
说明:
allOf返回的是CompletableFuture<Void>,表示“所有任务完成”,但不返回具体结果。你需要手动调用get()获取每个任务的结果。
anyOf:只要有一个完成就返回
CompletableFuture<String> fastTask = CompletableFuture.supplyAsync(() -> {
System.out.println("快速任务开始");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "快速任务完成";
});
CompletableFuture<String> slowTask = CompletableFuture.supplyAsync(() -> {
System.out.println("慢速任务开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "慢速任务完成";
});
// 只要有一个完成就返回
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(fastTask, slowTask);
anyFuture.thenAccept(result -> {
System.out.println("最先完成的任务结果:" + result);
}).join();
说明:
anyOf会立即返回第一个完成的任务结果。这在实现超时机制、快速失败策略时非常有用。
实际应用场景:构建一个异步数据聚合服务
让我们用一个真实场景来整合所学内容。假设我们要构建一个用户信息聚合服务,需要从三个不同来源获取数据:
- 用户基本信息(来自 API)
- 用户订单信息(来自数据库)
- 用户偏好设置(来自缓存)
我们希望:
- 并行获取数据
- 即使某个来源失败,也不影响整体流程
- 最终返回聚合结果
public class UserDataAggregator {
public CompletableFuture<UserData> aggregateUserData() {
// 1. 获取用户基本信息
CompletableFuture<UserInfo> userInfoFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("正在获取用户基本信息...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (Math.random() > 0.7) {
throw new RuntimeException("API 超时");
}
return new UserInfo("张三", 25, "北京");
});
// 2. 获取订单信息
CompletableFuture<OrderInfo> orderInfoFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("正在获取订单信息...");
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new OrderInfo("1001", "2024-04-01", 99.9);
});
// 3. 获取偏好设置
CompletableFuture<Preference> preferenceFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("正在获取偏好设置...");
try {
Thread.sleep(800);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new Preference("dark", true);
});
// 使用 handle 统一处理异常,避免链中断
CompletableFuture<UserInfo> safeUserInfo = userInfoFuture.handle((info, ex) -> {
if (ex != null) {
System.err.println("用户信息获取失败:" + ex.getMessage());
return new UserInfo("匿名用户", 0, "未知");
}
return info;
});
// 合并所有结果
return safeUserInfo
.thenCombine(orderInfoFuture, (userInfo, order) -> new UserData(userInfo, order, null))
.thenCombine(preferenceFuture, (userData, preference) -> {
userData.setPreference(preference);
return userData;
})
.whenComplete((result, exception) -> {
if (exception != null) {
System.err.println("聚合失败:" + exception.getMessage());
} else {
System.out.println("用户数据聚合成功:" + result);
}
});
}
}
// 简化类定义(仅用于演示)
class UserInfo {
String name;
int age;
String city;
public UserInfo(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
}
class OrderInfo {
String id;
String date;
double amount;
public OrderInfo(String id, String date, double amount) {
this.id = id;
this.date = date;
this.amount = amount;
}
}
class Preference {
String theme;
boolean notification;
public Preference(String theme, boolean notification) {
this.theme = theme;
this.notification = notification;
}
}
class UserData {
UserInfo userInfo;
OrderInfo orderInfo;
Preference preference;
public UserData(UserInfo userInfo, OrderInfo orderInfo, Preference preference) {
this.userInfo = userInfo;
this.orderInfo = orderInfo;
this.preference = preference;
}
public void setPreference(Preference preference) {
this.preference = preference;
}
@Override
public String toString() {
return "UserData{userInfo=" + userInfo.name + ", order=" + orderInfo.id + ", theme=" + preference.theme + "}";
}
}
说明:这个例子展示了 Java 9 改进的 CompletableFuture API 如何在真实项目中协同工作。通过
handle处理异常、thenCombine合并结果、whenComplete记录状态,整个流程既健壮又可读。
总结:为什么 Java 9 的改进值得你关注
Java 9 改进的 CompletableFuture API 并不是颠覆性的,但却是极其实用的。它让异步编程从“能用”走向“好用”。
handle和whenComplete提供了更清晰的异常处理与副作用控制- 支持自定义执行器,提升线程管理灵活性
allOf和anyOf更易用,适合复杂场景组合- 整体 API 更加一致,减少了学习成本
对于初学者而言,掌握这些方法能让你写出更安全、更易维护的异步代码;对于中级开发者,它们是构建高性能服务端应用的利器。
如果你还没升级到 Java 9 或更高版本,强烈建议尽快行动。这些改进虽然看似细微,却能在长期项目中带来巨大的开发效率提升。