Future 和 CompletableFuture 详解
【Java 并发编程】Future 和 CompletableFuture 详解:异步任务、链式调用、任务编排与面试总结
摘要
在 Java 并发编程中,Future 和 CompletableFuture 都是用来处理异步任务结果的重要工具。
Future 是 Java 5 引入的异步结果模型,主要用于获取线程池中异步任务的执行结果。
CompletableFuture 是 Java 8 引入的增强版异步编程工具,不仅可以获取异步结果,还支持链式调用、任务编排、多个任务组合以及异常处理。
本文将从面试角度出发,详细讲解 Future 和 CompletableFuture 的作用、基本使用、核心方法、区别、项目场景以及面试回答方式。
–
目录
- 一、为什么需要异步任务?
- 二、Future 是什么?
- 三、Future 的基本使用
- 四、Future 常用方法
- 五、Future 的缺点
- 六、CompletableFuture 是什么?
- 七、CompletableFuture 的基本使用
- 八、get 和 join 的区别
- 九、CompletableFuture 的链式调用
- 十、thenCompose:处理有依赖关系的异步任务
- 十一、thenCombine:合并两个独立任务的结果
- 十二、allOf:等待多个任务全部完成
- 十三、anyOf:任意一个任务完成即可
- 十四、CompletableFuture 的异常处理
- 十五、Async 和非 Async 的区别
- 十六、为什么 CompletableFuture 要指定自定义线程池?
- 十七、实际项目场景:商品详情页接口聚合
- 十八、Future 和 CompletableFuture 的区别
- 十九、面试回答:Future 怎么说?
- 二十、面试回答:CompletableFuture 怎么说?
- 二十一、最终总结
- 二十二、最精简面试背诵版
一、为什么需要异步任务?
在实际项目中,很多操作是比较耗时的,比如:
查询数据库
调用第三方接口
调用 RPC 服务
发送短信
生成报表
文件上传下载
如果这些任务都由主线程同步执行,那么主线程就会一直阻塞等待,接口响应时间会变长,系统吞吐量也会下降。
比如一个商品详情页接口,需要查询:
商品基本信息:50ms
库存信息:80ms
优惠券信息:100ms
评价信息:120ms
推荐商品:150ms
如果串行执行,总耗时大概是:
50 + 80 + 100 + 120 + 150 = 500ms
但是这些查询之间如果没有依赖关系,就可以并行执行。
这样总耗时接近最慢的那个任务,也就是大约 150ms。
这就是异步编程的价值:
把多个互不依赖的耗时任务并行执行,提高系统吞吐量,降低接口响应时间。
二、Future 是什么?
Future 可以理解成:
异步任务执行结果的占位符。
当我们把一个任务提交给线程池后,任务不一定马上执行完成。
但是线程池会立即返回一个 Future 对象。
这个 Future 对象代表未来某个时间点可以拿到的任务结果。
简单来说:
任务提交时,结果还没出来;
Future 先返回;
等任务执行完成后,可以通过 Future 获取结果。
三、Future 的基本使用
Future 通常配合线程池和 Callable 使用。
import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(() -> {
Thread.sleep(1000);
return "查询用户成功";
});
System.out.println("主线程继续执行");
String result = future.get();
System.out.println("异步任务结果:" + result);
executorService.shutdown();
}
}
执行流程如下:
1. 主线程把任务提交给线程池
2. 线程池中的子线程执行任务
3. 主线程可以继续执行其他逻辑
4. 当主线程需要结果时,调用 future.get()
5. 如果异步任务还没执行完,get() 会阻塞等待
6. 子线程执行完成后,主线程拿到结果
四、Future 常用方法
1. get()
get() 用来获取异步任务的执行结果。
String result = future.get();
如果任务还没有执行完成,调用 get() 的线程会阻塞等待。
2. get(timeout, unit)
带超时时间获取结果。
String result = future.get(2, TimeUnit.SECONDS);
如果 2 秒内任务还没有返回结果,就会抛出 TimeoutException。
在实际项目中,建议使用带超时时间的 get(),避免线程无限等待。
3. isDone()
判断任务是否执行完成。
if (future.isDone()) {
String result = future.get();
}
4. cancel()
取消任务。
future.cancel(true);
参数 true 表示如果任务正在运行,尝试中断执行该任务的线程。
5. isCancelled()
判断任务是否被取消。
boolean cancelled = future.isCancelled();
五、Future 的缺点
虽然 Future 可以获取异步任务的结果,但是它也有一些明显缺点。
1. get() 会阻塞
这是 Future 最大的问题。
String result = future.get();
如果任务还没有完成,当前线程会一直阻塞等待。
这样异步代码在获取结果时又变成了同步等待。
2. 不支持链式调用
假设有这样一个需求:
先查询用户信息
再根据用户信息查询订单
再根据订单信息计算优惠
使用 Future 实现时比较麻烦,因为 Future 本身不支持链式调用。
3. 不方便组合多个异步任务
比如现在有三个任务:
查询用户信息
查询订单信息
查询优惠券信息
如果使用 Future,通常需要一个个调用 get() 获取结果,代码不够优雅。
4. 异常处理不方便
Future 的异常通常要在 get() 的时候捕获。
try {
String result = future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
}
异常处理和业务逻辑容易混在一起。
六、CompletableFuture 是什么?
CompletableFuture 是 Java 8 引入的异步编程工具。
它实现了 Future 接口,可以看作是 Future 的增强版。
CompletableFuture 不仅可以获取异步结果,还支持:
链式调用
任务串行编排
任务并行组合
多个任务结果合并
异常处理
手动完成任务
所以在复杂异步场景下,CompletableFuture 比 Future 更常用。
七、CompletableFuture 的基本使用
1. runAsync:没有返回值的异步任务
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("异步执行任务");
});
future.join();
}
}
runAsync() 适合执行没有返回值的异步任务,比如:
异步打印日志
异步发送通知
异步缓存预热
2. supplyAsync:有返回值的异步任务
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "查询用户成功";
});
String result = future.join();
System.out.println(result);
}
}
supplyAsync() 适合执行有返回值的异步任务。
八、get 和 join 的区别
CompletableFuture 既可以使用 get(),也可以使用 join() 获取结果。
future.get();
future.join();
它们的区别如下:
| 方法 | 说明 |
|---|---|
get() |
会抛出受检异常,需要 try-catch 或 throws |
join() |
抛出运行时异常,不强制捕获 |
示例:
try {
String result = future.get();
} catch (Exception e) {
e.printStackTrace();
}
使用 join():
String result = future.join();
面试中可以这样说:
get 和 join 都会阻塞等待结果。
区别是 get 会抛受检异常,join 会把异常包装成运行时异常 CompletionException。
九、CompletableFuture 的链式调用
CompletableFuture 比 Future 强大的地方就在于它支持链式调用。
1. thenApply:接收上一步结果,并返回新结果
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
return "user";
})
.thenApply(user -> {
return user + " order";
});
System.out.println(future.join());
执行流程:
第一步返回 user
第二步拿到 user,处理成 user order
thenApply 适合对上一步结果做转换。
2. thenAccept:接收上一步结果,但没有返回值
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> {
return "user";
})
.thenAccept(user -> {
System.out.println("拿到用户:" + user);
});
future.join();
thenAccept 适合消费结果,比如打印日志、发送消息等。
3. thenRun:不接收上一步结果,也没有返回值
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> {
return "user";
})
.thenRun(() -> {
System.out.println("继续执行下一步");
});
future.join();
thenRun 不关心上一步的执行结果,只是在上一步完成之后继续执行。
4. thenApply、thenAccept、thenRun 的区别
| 方法 | 是否接收上一步结果 | 是否有返回值 |
|---|---|---|
thenApply |
接收 | 有 |
thenAccept |
接收 | 无 |
thenRun |
不接收 | 无 |
可以这样记:
thenApply:有入参,有返回值
thenAccept:有入参,无返回值
thenRun:无入参,无返回值
十、thenCompose:处理有依赖关系的异步任务
如果第二个异步任务依赖第一个异步任务的结果,可以使用 thenCompose。
比如:
先查用户
再根据用户查订单
代码如下:
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
return "userId=1001";
})
.thenCompose(userId -> {
return CompletableFuture.supplyAsync(() -> {
return "订单信息," + userId;
});
});
System.out.println(future.join());
面试中可以这样说:
thenCompose 用来处理有依赖关系的异步任务,后一个任务依赖前一个任务的结果。
十一、thenCombine:合并两个独立任务的结果
如果两个任务互不依赖,可以并行执行,然后合并结果。
比如:
查询用户信息
查询订单信息
两个任务可以并行执行
最后合并结果
代码如下:
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
return "用户信息";
});
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
return "订单信息";
});
CompletableFuture<String> resultFuture = userFuture.thenCombine(orderFuture, (user, order) -> {
return user + " + " + order;
});
System.out.println(resultFuture.join());
面试中可以这样说:
thenCombine 适合两个没有依赖关系的异步任务并行执行,然后把两个任务结果合并。
十二、allOf:等待多个任务全部完成
如果有多个异步任务,需要等待它们全部完成,可以使用 allOf()。
CompletableFuture<String> userFuture =
CompletableFuture.supplyAsync(() -> "用户信息");
CompletableFuture<String> orderFuture =
CompletableFuture.supplyAsync(() -> "订单信息");
CompletableFuture<String> couponFuture =
CompletableFuture.supplyAsync(() -> "优惠券信息");
CompletableFuture<Void> allFuture = CompletableFuture.allOf(
userFuture,
orderFuture,
couponFuture
);
allFuture.join();
String user = userFuture.join();
String order = orderFuture.join();
String coupon = couponFuture.join();
System.out.println(user);
System.out.println(order);
System.out.println(coupon);
allOf() 非常适合接口聚合场景。
比如商品详情页接口,需要同时查询:
商品基本信息
库存信息
优惠券信息
评论信息
推荐商品
这些任务互不依赖,就可以并行查询。
十三、anyOf:任意一个任务完成即可
anyOf() 表示多个任务中,只要有一个任务完成,就返回结果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
sleep(3000);
return "接口1返回";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "接口2返回";
});
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
System.out.println(anyFuture.join());
辅助方法:
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
anyOf() 适合:
多个数据源竞争
多个接口取最快返回
容灾兜底
十四、CompletableFuture 的异常处理
1. exceptionally:异常兜底
如果前面的任务发生异常,可以使用 exceptionally 返回一个默认结果。
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
int i = 1 / 0;
return "success";
})
.exceptionally(e -> {
return "默认结果";
});
System.out.println(future.join());
输出:
默认结果
2. handle:成功或失败都会执行
handle 可以同时处理正常结果和异常。
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
int i = 1 / 0;
return "success";
})
.handle((result, exception) -> {
if (exception != null) {
return "异常兜底结果";
}
return result;
});
System.out.println(future.join());
3. whenComplete:任务完成后回调
whenComplete 可以在任务完成后执行一些额外逻辑,比如打日志、监控、清理资源。
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
return "success";
})
.whenComplete((result, exception) -> {
System.out.println("任务完成,result = " + result);
});
System.out.println(future.join());
需要注意:
whenComplete 通常不改变原来的执行结果。
4. 三种异常处理方式区别
| 方法 | 是否能感知异常 | 是否能返回新结果 | 典型用途 |
|---|---|---|---|
exceptionally |
可以 | 可以 | 异常兜底 |
handle |
可以 | 可以 | 成功失败都处理 |
whenComplete |
可以 | 通常不改变结果 | 打日志、监控、清理资源 |
十五、Async 和非 Async 的区别
CompletableFuture 很多方法都有两个版本:
thenApply()
thenApplyAsync()
区别如下:
1. thenApply
future.thenApply(result -> {
return result + " after";
});
不带 Async 的方法,通常由上一个阶段的线程继续执行。
2. thenApplyAsync
future.thenApplyAsync(result -> {
return result + " after";
});
带 Async 的方法,会把后续任务提交到线程池中异步执行。
如果不指定线程池,默认使用:
ForkJoinPool.commonPool()
实际项目中更推荐传入自定义线程池:
future.thenApplyAsync(result -> {
return result + " after";
}, executor);
面试中可以这样回答:
不带 Async 的方法通常由前一个阶段的线程继续执行;
带 Async 的方法会把任务提交到线程池异步执行。
如果不指定线程池,默认使用 commonPool,生产环境最好显式指定自定义线程池。
十六、为什么 CompletableFuture 要指定自定义线程池?
如果直接这样写:
CompletableFuture.supplyAsync(() -> {
return "hello";
});
默认使用的是:
ForkJoinPool.commonPool()
这个公共线程池是全局共享的。
实际项目中不建议大量业务都使用默认公共线程池,原因有:
1. 不同业务共用一个线程池,容易互相影响
2. 线程数不可控
3. 线程名不清晰,不方便线上排查
4. 不方便做业务隔离和监控
推荐写法:
import java.util.concurrent.*;
public class CompletableFutureThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executor = new ThreadPoolExecutor(
8,
16,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
r -> new Thread(r, "order-async-pool-" + r.hashCode()),
new ThreadPoolExecutor.CallerRunsPolicy()
);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "查询订单";
}, executor);
System.out.println(future.join());
executor.shutdown();
}
}
面试中可以这样说:
实际项目中使用 CompletableFuture 时,我会传入自定义线程池,而不是使用默认的 ForkJoinPool.commonPool。
这样可以避免不同业务互相影响,也方便线程池隔离、监控和问题排查。
十七、实际项目场景:商品详情页接口聚合
比如电商系统中,商品详情页需要返回:
商品基本信息
库存信息
优惠券信息
评价信息
推荐商品
如果串行查询:
商品信息 50ms
库存信息 80ms
优惠券 100ms
评价 120ms
推荐 150ms
总耗时大约 500ms
如果使用 CompletableFuture 并行查询,总耗时接近最慢的任务:
总耗时大约 150ms
示例代码:
import java.util.concurrent.*;
public class ProductDetailDemo {
private static final ExecutorService executor = new ThreadPoolExecutor(
10,
20,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
r -> new Thread(r, "product-detail-pool-" + r.hashCode()),
new ThreadPoolExecutor.CallerRunsPolicy()
);
public static void main(String[] args) {
CompletableFuture<String> productFuture =
CompletableFuture.supplyAsync(() -> queryProduct(), executor);
CompletableFuture<String> stockFuture =
CompletableFuture.supplyAsync(() -> queryStock(), executor);
CompletableFuture<String> couponFuture =
CompletableFuture.supplyAsync(() -> queryCoupon(), executor);
CompletableFuture<String> commentFuture =
CompletableFuture.supplyAsync(() -> queryComment(), executor);
CompletableFuture<Void> allFuture = CompletableFuture.allOf(
productFuture,
stockFuture,
couponFuture,
commentFuture
);
allFuture.join();
String product = productFuture.join();
String stock = stockFuture.join();
String coupon = couponFuture.join();
String comment = commentFuture.join();
System.out.println(product);
System.out.println(stock);
System.out.println(coupon);
System.out.println(comment);
executor.shutdown();
}
private static String queryProduct() {
sleep(50);
return "商品信息";
}
private static String queryStock() {
sleep(80);
return "库存信息";
}
private static String queryCoupon() {
sleep(100);
return "优惠券信息";
}
private static String queryComment() {
sleep(120);
return "评价信息";
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
这个案例在面试中非常好用。
可以这样描述:
商品详情页接口需要查询多个下游服务,这些服务之间没有依赖关系。
如果串行查询,接口耗时会比较长。
所以可以使用 CompletableFuture 并行查询,然后通过 allOf 等待全部完成,最后统一组装结果。
这样可以把总耗时从多个接口耗时之和,优化成接近最慢的那个接口耗时。
十八、Future 和 CompletableFuture 的区别
| 对比点 | Future | CompletableFuture |
|---|---|---|
| 出现时间 | Java 5 | Java 8 |
| 是否能获取异步结果 | 可以 | 可以 |
| 获取结果是否阻塞 | get 会阻塞 | get/join 也会阻塞 |
| 是否支持链式调用 | 不支持 | 支持 |
| 是否支持任务组合 | 不方便 | 支持 |
| 是否支持任务编排 | 不方便 | 支持 |
| 异常处理 | 不方便 | 支持 exceptionally、handle、whenComplete |
| 多任务合并 | 不方便 | 支持 allOf、anyOf、thenCombine |
| 使用场景 | 简单异步任务 | 复杂异步编排 |
十九、面试回答:Future 怎么说?
如果面试官问 Future,可以这样回答:
Future 是 Java 早期提供的异步结果接口,通常配合 Callable 和线程池使用。
提交一个 Callable 任务后,线程池会返回一个 Future 对象。
Future 表示未来某个时间点可以拿到的任务结果。
我们可以通过 get 方法获取结果,通过 isDone 判断任务是否完成,通过 cancel 取消任务。
但是 Future 的缺点是 get 方法会阻塞,不支持链式调用,也不方便组合多个异步任务,异常处理也不够优雅。
所以复杂异步场景下更常用 CompletableFuture。
二十、面试回答:CompletableFuture 怎么说?
如果面试官问 CompletableFuture,可以这样回答:
CompletableFuture 是 Java 8 引入的异步编程工具,它实现了 Future 接口,可以看作是 Future 的增强版。
它不仅可以获取异步任务结果,还支持链式调用、任务编排、多个任务组合和异常处理。
比如 thenApply 可以对上一步结果做转换,thenAccept 可以消费结果,thenCompose 可以处理有依赖关系的异步任务,thenCombine 可以合并两个并行任务结果,allOf 可以等待多个任务全部完成,anyOf 可以等待任意一个任务完成。
异常方面,可以通过 exceptionally、handle、whenComplete 进行处理。
实际项目中,CompletableFuture 常用于接口聚合、并行查询、异步调用下游服务等 IO 密集型场景。
使用时要注意传入自定义线程池,不要直接使用默认的 ForkJoinPool.commonPool,避免线程不可控和业务之间互相影响。
二十一、最终总结
Future 和 CompletableFuture 都是 Java 中处理异步任务结果的重要工具。
Future 是基础版,主要用于:
提交异步任务
获取异步结果
判断任务是否完成
取消任务
但是它的缺点是:
get 会阻塞
不支持链式调用
不方便组合多个任务
异常处理不够优雅
CompletableFuture 是增强版,除了具备 Future 的能力,还支持:
链式调用
串行任务编排
并行任务组合
多个任务结果合并
异常兜底处理
所以在实际项目中:
简单异步任务可以使用 Future;
复杂异步编排、接口聚合、并行查询更推荐使用 CompletableFuture。
尤其在电商、订单、商品详情页、数据聚合接口中,CompletableFuture 非常常见。
最后需要注意:
生产环境使用 CompletableFuture 时,最好指定自定义线程池,不要直接使用默认 commonPool。
二十二、最精简面试背诵版
Future 和 CompletableFuture 都是用来处理异步任务结果的。
Future 是 Java 5 提供的基础接口,通常配合 Callable 和线程池使用。
它可以通过 get 获取结果,通过 isDone 判断是否完成,通过 cancel 取消任务。
但是 Future 的 get 是阻塞的,不支持链式调用,也不方便组合多个异步任务。
CompletableFuture 是 Java 8 引入的增强版 Future。
它不仅可以获取异步结果,还支持链式调用、任务编排、多个任务组合和异常处理。
比如 thenApply 做结果转换,thenCompose 做串行依赖,thenCombine 合并两个任务结果,allOf 等待多个任务全部完成,anyOf 等待任意一个任务完成。
异常处理可以使用 exceptionally、handle、whenComplete。
实际项目中,如果只是简单提交一个任务并等待结果,用 Future 就够了;
如果涉及多个异步任务的并行执行、依赖编排、结果合并和异常兜底,更适合使用 CompletableFuture。
并且在生产环境使用 CompletableFuture 时,最好传入自定义线程池,而不是使用默认的 ForkJoinPool.commonPool。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)