1 简介
CompletableFuture 是 JDK8 中新增的多线程任务执行类,通过它我们可以方便地进行串行、并行、组合和转换异步任务。它能够以一种非常灵活的方式处理异步操作的结果,包括成功的结果、异常和取消等情况。接下来,我们就详细了解一下这个类。
2 具体方法及使用
在正常的业务代码开发中,如果我们需要使用子线程处理数据通常需要使用线程池,但手动创建线程池很麻烦,而且还需要注意销毁。在 JDK8 中 CompletableFuture就很好的替我们解决了这个问题。下面我们来看一下如何使用这个类。
实例化
代码语言:javascript复制public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
supplyAsync和runAsync区别在于supplyAsync会返回一个CompletableFuture对象,而runAsync不会,此外在进行实例化时可以指定线程池
代码语言:javascript复制CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 执行具有返回值的任务 return "灵墨AI探索室";});CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 执行没有返回值的任务});
ExecutorService customExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "灵墨AI探索室";}, customExecutor);
获取结果
代码语言:javascript复制public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
get和join方法都是阻塞当前线程,直到获取接口 但join方法不会显式的抛出异常,更适合在流式编程中使用,get方法还可以指定等待时间,超时抛出异常。
代码语言:javascript复制CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 执行具有返回值的任务
return "灵墨AI探索室";});
future.get();
//需处理异常
future.join();
future.get(1, TimeUnit.MINUTES);//get方法可以指定等待时间
下一步处理
代码语言:javascript复制public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
这几个方法都是用来在CompletableFuture完成后进行下一步处理,这里使用thenApply举一个例子
代码语言:javascript复制CompletableFuture<Score> future = CompletableFuture.supplyAsync(() -> {
// 获取学生信息
return StudentService.getStudent(id);
}).thenApply(student -> {
// 再根据学生信息获取考试分数
return ScoreServcie.getScore(student);
});
异常处理
代码语言:javascript复制public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
这两个方法是用来处理CompletableFuture执行过程中的异常。
此外CompletableFuture还提供了anyOf,allOf等方法用来统一对多个任务进行处理。
相信各位小伙伴看到这里已经对CompletableFuture有了大致的了解,上面列举的方法不算全面,但已经能够覆盖开发中的大部分场景了,下面我们就详细了解一下CompletableFuture的内部结构。
3 实现原理
我们先看一下CompletableFuture 的类图关系 它继承了Future和CompletionStage两个类
在CompletableFuture 中有两个属性值值得关注
代码语言:javascript复制public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
volatile Object result;
volatile Completion stack;
}
其中result 用来保存future的结果,stack是利用链表实现的栈,记录了这个future的后续动作,在使用CompletableFuture 时,每次调用都会生成一个新的CompletableFuture 这些future就是通过stack记录的信息串联起来的,我们来举一个例子
代码语言:javascript复制CompletableFuture.supplyAsync(() -> 10).thenApply(i -> i 10).thenApply(i -> i 20);
在这行代码中 各CompletableFuture 对象的结构如下图表示。
有些同学看到这里可能有些疑惑,这种结构为什么不直接使用一个指针,而是使用栈。这是因为CompletableFuture 同时支持多个后续任务。
同步任务和异步任务
在CompletableFuture 中提供的几乎每一个同步方法都会对应提供一个异步方法,从接口名上就很容易分辨,比如下面这几个
thenRun | thenRunAsync |
---|---|
thenAccept | thenRunAsync |
thenApply | thenApplyAsync |
此处需要注意的是,异步任务在任何情况下都会在线程池中运行,而同步任务的前置任务如果是异步任务,同步任务也会在线程池中运行
图中的蓝色的是异步任务 黄色的是同步任务,而由于2的前置任务1是异步任务,所以2、3和1都会在同一个异步线程中执行,4、5也同理 只不过由于4是异步任务导致4、5和1、2、3不在同一个线程中。
此外,2、3是同步任务 所有2、3是在同一个线程中按照出栈顺序执行的,如果2、3变为异步任务,则执行顺序是不固定的。
CompletableFuture 在使用起来十分便捷,但也要注意,由于服务器的cpu核数是有限的,如果使用异步的地方过多,最终也会导致阻塞,各位小伙伴在使用时也多加注意,希望各位小伙伴能点个关注,这对我们是一种很大的鼓励。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!