目录
前言
它们都是异步执行任务,但设计目标、能力范围和编程体验有很大不同,我帮你详细拆开来看,对比到“为什么很多人新项目更倾向于 CompletableFuture”。
本质上是在对比两种提交异步任务的方式:
- ExecutorService.submit(...)(返回 Future)
- CompletableFuture.supplyAsync(...)(返回 CompletableFuture)
一句话记忆:
Future 是“我去等你做完再干别的”,
CompletableFuture 是“你做好了自动告诉我,然后我继续做下去”。
1、Future
1.1、定义
Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,就可以通过Future把这个任务放到异步线程中执行。
主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。future+线程池异步多线程任务配合,能显著提高程序的执行效率。
1.2、常用方法:
1、boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
①参数:
mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;允许正在运行的任务运行完成, 则为false
②返回:
如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true
2、V get()
如有必要,等待计算完成,然后获取其结果。
3、V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
4、boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。
5、boolean isDone()
如果任务已完成,则返回 true。
代码示例:
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
// 2.创建Callable类型的任务对象
Future<Integer> f = es.submit(new MyCall(1, 1));
// 3.判断任务是否已经完成
boolean done = f.isDone();
System.out.println("判断任务是否完成"+done);
// 4.任务执行的结果
Integer v = f.get();
System.out.println("任务执行的结果是"+v);
// 5.判断取消任务的结果
boolean b = f.cancel(true);
System.out.println("取消任务执行的结果"+b);
} }
class MyCall implements Callable<Integer> {
private int a;
private int b;
public MyCall(int a, int b) {
this.a = a;
this.b = b;}
@Override
public Integer call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name + "准备开始计算...");
Thread.sleep(2000);
System.out.println("计算完成...");
return a + b;
} }
ExecutorService + Future
ExecutorService threadPool = Executors.newFixedThreadPool(3);
Future<?> future = threadPool.submit(() -> {
System.out.println("任务执行");
});
// Future 只能阻塞式获取结果
Object result = future.get(); // 阻塞直到结束
Future接口作为Java中处理异步任务的一种方式,提供了获取结果、取消任务等方法,但存在阻塞和轮询的缺点。
2、CompletableFuture
2.1、设计背景
get()方法在Future计算完成之前会一直处在阻塞状态下,阻塞的方式和异步编程的设计理念相违背;而isDone()轮询的方式会消耗无谓的CPU资源,而且也不见的能及时地得到计算结果。
对于真正的异步处理我们希望是可以通过传入回调参数,在future结束时自动调用该回调函数,这样我们就不用等待结果。因此,JDK8设计出了CompletableFuture。
CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
2.2、常用方法
CompletableFuture.supplyAsync(...)
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("任务执行");
return "任务结果";
});
// 可以链式异步处理,不阻塞主线程
cf.thenAccept(result -> System.out.println("结果: " + result));
CompletableFuture为了解决Future的不足,提供了非阻塞的解决方案,通过CompletionStage和静态方法如runAsync和supplyAsync支持回调和链式操作,提高了异步编程的灵活性。
3、区别
如下所示:
3.1、行为上的具体差别
1、Future(ExecutorService方式)
- submit()提交任务 → 返回 Future
- get() 会阻塞直到任务结束(同步等待结果)
- 如果你不调用get(),就相当于只是异步跑了个任务,但没法在任务结束时主动触发后续逻辑
- 适合**"跑完就拿结果"**的场景
例子:
ExecutorService pool = Executors.newFixedThreadPool(3);
Future<Integer> f1 = pool.submit(() -> 100);
System.out.println(f1.get()); // 阻塞拿结果
2、CompletableFuture 方式
- supplyAsync() 提交任务 → 返回 CompletableFuture
- 你可以在 future 完成后自动触发回调,而且线程切换和数据传递内置支持
- 基于函数式 API,可以写成任务编排、组合的风格
例子:
CompletableFuture.supplyAsync(() -> 100)
.thenApply(res -> res * 2) // 链式处理
.thenAccept(System.out::println); // 最终消费
输出:
200
整个过程不用自己管理阻塞和线程切换,比 Future 灵活得多。
3.2、阻塞 vs 非阻塞
Future:
获取结果必须 get()(阻塞当前线程)
CompletableFuture:
可以用 thenXXX 在另一个线程里异步接收结果,不一定阻塞主线程
代码示例如下:
// Future 阻塞示例
Future<Integer> f = pool.submit(() -> 1+1);
System.out.println(f.get()); // 当前线程卡着等结果
// CompletableFuture 非阻塞示例
CompletableFuture.supplyAsync(() -> 1+1)
.thenAccept(System.out::println); // 主线程可继续做别的
3.3、线程池使用差别
1、Future:
必须自己传一个 ExecutorService(比如 Executors.newFixedThreadPool)。
2、CompletableFuture:
如果不指定线程池,默认用 ForkJoinPool.commonPool()
也可以这样自定义:
CompletableFuture.supplyAsync(() -> 123, myExecutor);
3.4、异常处理的差别
Future:
try {
future.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
只能用 try-catch,在 get() 时集中处理。
CompletableFuture:
CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("出错了");
return 123;
}).exceptionally(ex -> {
System.out.println("异常: " + ex.getMessage());
return -1;
});
3.5、使用对比
你的代码:
ExecutorService threadPool = Executors.newFixedThreadPool(3);
Future<?> future = threadPool.submit(() -> {
System.out.println("111");
});
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("111");
});
区别:
1、submit
返回一个 Future,不能直接链式加后续逻辑,要等它完成用 get() 再处理。
2、CompletableFuture)
可以直接 .thenRun(...)、.thenApply(...)接后续逻辑,避免阻塞,也更适合任务组合。
总结
1、ExecutorService + Future
偏底层;适合“一次提交一次取结果”的简单异步;
必须手动拿结果、处理异常;
任务之间的依赖、组合要自己写控制逻辑
2、CompletableFuture
偏高层 & 响应式;
支持链式任务编排(组合、依赖、异常处理);
适合复杂异步流程;对比 Future 减少了阻塞等待;
参考文章