一、Future 类的作用
Future 是 Java 1.5 引入的接口,代表异步计算的结果,核心作用是:
-
获取异步任务结果 :通过
get()方法阻塞等待任务完成并获取结果 -
检查任务状态 :
isDone()判断是否完成,isCancelled()判断是否被取消 -
取消任务 :
cancel(boolean mayInterruptIfRunning)尝试取消未完成的任务 -
实现"将来式"模式 :调用线程提交任务后立即返回一个"凭证"(Future),稍后再凭此获取结果
典型使用场景:
java
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
// 模拟耗时计算
Thread.sleep(2000);
return 42;
});
// 主线程可以继续做其他事情...
System.out.println("任务已提交,继续执行其他操作");
// 需要结果时再获取(会阻塞直到任务完成)
Integer result = future.get(); // 阻塞等待
System.out.println("计算结果: " + result);
⚠️ Future 的局限性 :
get()是阻塞式调用,无法链式编排多个异步任务,异常处理不够灵活。
二、Callable 和 Future 的关系
它们是生产者-消费者的关系:
| 对比项 | Callable | Future |
|---|---|---|
| 角色 | 任务生产者 :定义可返回结果的异步任务 | 结果消费者 :持有任务结果的"凭证" |
| 核心方法 | V call():执行任务并返回结果,可抛出异常 |
V get():获取结果;boolean cancel():取消任务 |
| 与Runnable区别 | Runnable 无返回值、不能抛出受检异常;Callable 可返回结果且支持异常 | Future 专为配合 Callable 设计(也支持 Runnable) |
工作流程:
java
// 1. 定义任务(Callable - 生产结果)
Callable<String> task = () -> {
Thread.sleep(1000);
return "Hello Future";
};
// 2. 提交任务到线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(task); // submit返回Future
// 3. 获取结果(Future - 消费结果)
String result = future.get(); // 阻塞直到call()执行完成
💡 关键理解 :
Callable负责产生 结果,Future负责获取 结果,二者通过ExecutorService.submit()关联
三、CompletableFuture 类的作用
CompletableFuture 是 Java 8 引入的增强版 Future,解决了传统 Future 的痛点,核心优势:
1. 非阻塞式异步编排
java
// 传统Future:必须阻塞等待
future.get(); // 阻塞!
// CompletableFuture:回调式处理
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World") // 链式转换
.thenAccept(System.out::println); // 结果消费(不阻塞主线程)
2. 强大的任务编排能力
java
// 场景:同时查询用户信息和订单信息,最后合并结果
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> getUserById(1));
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> getOrdersByUserId(1));
// thenCombine:两个异步任务都完成后合并结果
CompletableFuture<String> result = userFuture.thenCombine(orderFuture, (user, order) ->
"用户" + user.getName() + "有" + order.getItems().size() + "个订单"
);
result.thenAccept(System.out::println); // 异步打印,不阻塞
3. 实际应用场景
-
并行聚合 :首页加载需调用10+个微服务(用户信息、商品推荐、优惠券等),用
CompletableFuture.allOf()并行执行 -
流水线处理 :订单支付 → 更新库存 → 发送通知,用
thenApply/thenCompose串行编排 -
超时控制 :
orTimeout(3, TimeUnit.SECONDS)避免长时间阻塞 -
异常处理 :
exceptionally()/handle()统一处理链路中的异常
4. 与 Future 对比
| 特性 | Future | CompletableFuture |
|---|---|---|
| 获取结果 | 阻塞式 get() |
非阻塞回调(thenApply等) |
| 任务编排 | 不支持 | 支持链式调用、组合、并行 |
| 异常处理 | 需手动 try-catch | 内置 exceptionally()/handle() |
| 主动完成 | 仅任务自身完成 | 可通过 complete() 主动设置结果 |
四 . 如何使用CompletableFuture实现一个任务需要依赖另外两个任务执行完之后再执行,怎么设计?
任务依赖设计:A、B 完成后执行 C
✅ 推荐方案:CompletableFuture.allOf() + thenApply()
java
ExecutorService customPool = Executors.newFixedThreadPool(4);
// 任务A:获取用户信息(模拟耗时1秒)
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("【A】开始获取用户信息 - " + Thread.currentThread().getName());
Thread.sleep(1000);
return new User(1L, "张三");
}, customPool);
// 任务B:获取订单列表(模拟耗时1.5秒)
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("【B】开始获取订单列表 - " + Thread.currentThread().getName());
Thread.sleep(1500);
return Arrays.asList(new Order(101), new Order(102));
}, customPool);
// 任务C:依赖A和B的结果生成报表
CompletableFuture<Report> reportFuture = CompletableFuture.allOf(userFuture, orderFuture)
.thenApply(v -> {
// allOf 不返回结果,需手动获取
User user = userFuture.join(); // join() 不会抛出受检异常
List<Order> orders = orderFuture.join();
System.out.println("【C】开始生成报表 - " + Thread.currentThread().getName());
return new Report(user, orders);
});
// 获取最终结果(生产环境建议设置超时)
Report report = reportFuture.get(5, TimeUnit.SECONDS);
System.out.println("报表生成完成: " + report);
🔁 其他方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
thenCombine() |
仅依赖两个任务 | 语法简洁,自动传递结果 | 仅支持两个任务组合 |
allOf() + join() |
依赖N个任务 | 可组合任意数量任务 | 需手动调用 join() 获取结果 |
thenCompose() 嵌套 |
串行依赖(A→B→C) | 处理链式依赖 | 不适合并行场景 |
💡 最佳实践 :超过 2 个任务并行依赖时,优先用
allOf();仅 2 个任务可用thenCombine()简化代码。
五 . 使用 CompletableFuture,有一个任务失败,如何处理异常?
CompletableFuture 提供三种异常处理策略,按场景选择:
🛡️ 策略1:降级处理(推荐生产环境使用)
java
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new RuntimeException("服务异常");
return "成功结果";
}, customPool);
// 任务失败时返回默认值
CompletableFuture<String> withFallback = task.exceptionally(ex -> {
System.err.println("【降级】捕获异常: " + ex.getMessage());
return "默认结果"; // 降级返回
});
System.out.println(withFallback.join()); // 永远不会抛出异常
🔍 策略2:统一处理(记录日志 + 保留异常)
java
task.handle((result, ex) -> {
if (ex != null) {
System.err.println("【统一处理】任务失败: " + ex.getMessage());
// 可记录监控日志、发送告警
return "处理失败";
}
return "处理成功: " + result;
});
⚠️ 策略3:传播异常(快速失败)
java
// 不做异常处理,异常会向后传播
CompletableFuture<String> chained = task
.thenApply(s -> s + " → 步骤2")
.thenApply(s -> s + " → 步骤3");
try {
chained.join();
} catch (CompletionException e) {
System.err.println("【传播】最终捕获异常: " + e.getCause().getMessage());
}
📌 异常处理关键点
| 方法 | 是否消费异常 | 返回类型 | 适用场景 |
|---|---|---|---|
exceptionally() |
✅ 消费 | 与原任务相同 | 降级、默认值 |
handle() |
✅ 消费 | 自定义类型 | 统一日志/监控 |
whenComplete() |
❌ 不消费 | 原类型 | 仅记录日志,异常继续传播 |
| 不处理 | ❌ 不消费 | - | 快速失败,由最终调用方处理 |
⚠️ 重要 :
allOf()中任意一个任务失败,整个组合立即失败 。若需"部分失败仍继续",需对每个子任务单独做exceptionally()降级:
java
// 错误做法:一个失败,allOf 立即失败
CompletableFuture.allOf(failTask, successTask).join();
// 正确做法:每个任务单独降级
CompletableFuture<String> safeA = taskA.exceptionally(ex -> "A降级");
CompletableFuture<String> safeB = taskB.exceptionally(ex -> "B降级");
CompletableFuture.allOf(safeA, safeB).join(); // 即使A失败,B仍会执行
六 . 为什么必须自定义线程池?
❌ 默认线程池的风险(ForkJoinPool.commonPool())
java
// 危险!使用默认线程池
CompletableFuture.supplyAsync(() -> doSomething());
| 风险点 | 说明 | 后果 |
|---|---|---|
| 阻塞操作导致线程饥饿 | 默认池是工作窃取线程池,设计用于 CPU 密集型任务。若执行 I/O 阻塞(如 HTTP 调用、DB 查询),线程被挂起无法处理其他任务 | 线程池耗尽,整个应用卡死 |
| 与并行流共享资源 | Stream.parallel() 也使用 commonPool |
业务异步任务与数据处理互相争抢线程 |
| 无法监控和调优 | 线程数由系统决定(通常 = CPU 核心数 - 1) | 无法根据业务量动态调整 |
| 拒绝策略不可控 | 队列无界,极端情况 OOM | 系统稳定性差 |
✅ 正确做法:按任务类型分离线程池
java
// CPU 密集型(计算、加密等)
ExecutorService cpuPool = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").build()
);
// I/O 密集型(HTTP、DB、文件读写)
ExecutorService ioPool = new ThreadPoolExecutor(
20, // 核心线程数(可远大于CPU核心数)
50, // 最大线程数
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由调用线程执行,避免丢弃
);
// 使用示例
CompletableFuture.supplyAsync(() -> computeHash(data), cpuPool); // 计算任务
CompletableFuture.supplyAsync(() -> callRemoteApi(), ioPool); // 网络请求
📊 线程池配置建议
| 任务类型 | 核心线程数 | 队列类型 | 拒绝策略 | 说明 |
|---|---|---|---|---|
| CPU 密集型 | = CPU 核心数 | 有界队列 | AbortPolicy | 避免过多线程上下文切换 |
| I/O 密集型 | 2~5 × CPU 核心数 | 有界队列 | CallerRunsPolicy | 允许更多线程等待 I/O |
| 混合型 | 按比例拆分 | 分离队列 | 自定义 | 建议拆分为两个独立线程池 |
💡 黄金法则 :永远不要在 CompletableFuture 中使用阻塞操作而不自定义线程池。生产环境必须为不同类型任务配置独立线程池,并做好监控(如线程池活跃数、队列大小)。