1. 引言
在现代 Java 编程中,异步编程变得越来越重要。随着多核处理器的普及,充分利用多线程可以大大提高程序性能和用户体验。在这种情况下,Future 和 CompletableFuture 成为处理异步任务的核心工具。
2. Future 是什么?
Future 的定义及基本概念
Future 是 Java 并发库中的接口,表示一个异步计算的结果。使用 Future 可以在提交任务时立即返回一个对象,通过该对象可以检查任务的执行状态和结果。
Future 的使用流程
- 提交任务到 ExecutorService
- 获取 Future 对象
- 使用 Future.get() 方法阻塞等待任务执行完成并获取结果
Future 的局限性
- Future.get() 会阻塞当前线程直到任务完成
- 无法链式处理任务(给 CompletableFuture 铺个垫)
- 缺少对任务完成后的回调机制
- 无法处理任务执行中的异常
3. CompletableFuture 是什么?
CompletableFuture 的基本概念
CompletableFuture 是Java 8中引入的增强版Future,实现了Future 和 CompletionStage 接口,提供了一种异步编程的更优雅的方式。
CompletableFuture 的特点
- 支持链式调用,通过 thenApply、thenAccept 等方法进行链式处理
- 提供回调机制,允许在任务完成后自动执行其他操作
- 内置异常处理方法,如 exceptionally 和 handle
与 Future 的区别与增强点
- CompletableFuture 允许在非阻塞方式下检查任务的执行状态
- 支持组合多个异步任务的执行
- 提供更丰富的 API 用于异步任务管理
4. 使用示例
Future 使用示例
Future 相关方法:
- cancel(boolean):尝试取消任务的执行。true: 任务成功被取消, false 任务已经完成或者无法取消。
- isCancelled(): 检查任务是否被取消。true: 任务在完成之前就被取消了,否则返回 false。
- isDone(): 检查任务是否已经完成,不论正常、异常还是被取消。true: 任务已经完成,否则返回 false。
- get(): 阻塞当前线程,直到任务完成并返回结果。返回任务计算结果。
- get(long, TimeUnit): 与 get() 类似,但是会给定时间范围,若在时间范围内任务没有完成,会抛出TimeoutException。其他情况与 get() 一致。
示例代码:
public class Main {
private static ExecutorService executor = Executors.newFixedThreadPool(2); // 创建线程池
public static void main(String[] args) throws Exception {
long currentTime = System.currentTimeMillis(); // 获取当前时间戳
Future<Integer> future = executor.submit(() -> {
Thread.sleep(2000);
return 42;
}); // 提交任务到线程池,并且获得 Future 对象
Integer result = future.get(); // get() 方法会阻塞程序,直到任务执行完毕
long afterTime = System.currentTimeMillis();
System.out.println("spend time : " + (afterTime - currentTime) + " Result : " + result); // 输出执行时间以及获取到的结果
executor.shutdown(); // 关闭线程池
}
}
输出:
CompletableFuture 使用示例
CompletableFuture 相关方法非常之多,这里截图一部分:
示例代码:
public class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);// 任务1 返回 10
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);// 任务2 返回 20
CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> 30);// 任务3 返回 30
// 任务1计算完毕后调用的函数
future1.thenAccept((res) -> {
System.out.println("任务1计算完毕");
});
// 组合多个任务
CompletableFuture<Void> combineFuture = CompletableFuture.allOf(future1, future2, future3).thenRun(() -> {
int result = future1.join() + future2.join() + future3.join(); // join() 方法会阻塞线程, 但是这里阻塞的并不是我们的主线程, 下面会进行说明
System.out.println("组合计算后的结果为: " + result);
});
System.out.println("程序运行完毕");
}
}
执行结果:
说明:
- supplyAsync(): 参数为需要异步的任务
- thenAccept(): 表示 future1 对象任务执行后的回调函数
- CompletableFuture.allOf(): 等待一组任务全部执行完毕后执行的回调函数
注意!!CompletableFuture 有相当多的方法,这里不可能一一详尽,更多的还需要读者去一一尝试。
另外,关于上述的 join() 方法,这个方法也会阻塞线程,但是由于我们相关的回调函数并不是在主线程中执行的,因此 join 方法阻塞的并不是主线程,而是 ForkJoinPool.commonPool() 中分配到的线程(该线程池是共享线程池,本文不作过多描述,需要的读者可以自行搜索)。
5. Future 和 CompletableFuture 的区别是什么?
首先需要从基本概念了解他们的区别:
- Future:是一个接口,代表一个可能在未来某个时间完成的异步计算的结果。它的主要功能是获取计算结果,但不支持多个 Future 进行组合。
- CompeltableFuture:是 Future 的一个具体实现,提供了更强大的功能,它不仅支持异步计算,还支持将多个异步任务组合在一起,支持回调机制和链式操作。
1. 任务组合
- Future:不支持任务组合。
- CompletableFuture:允许使用 then、thenAccept、thenCombine、allof 等方法来组合多个异步任务。使得代码更加清晰以及便于维护。
2. 异常处理
- Future:使用 get() 方法获取结果时,如果计算出错,会抛出一个检查异常(ExecutionException)。处理异常比较繁琐。
- CompletableFuture:提供了 exceptionally 和 handle 方法来处理异常,使得异常处理更加灵活、优雅。
3. 回调机制
- Future:不支持回调函数,完成后需要通过 get() 方法阻塞扽得改。
- CompletableFuture:允许通过 then 系列方法注册回调函数,计算完成后可以立即执行。