CompletableFuture的使用与原理解析

举报
Kokomo 发表于 2023/12/26 09:35:32 2023/12/26
【摘要】 CompletableFuture的使用与原理解析

1 简介

CompletableFuture 是 JDK8 中新增的多线程任务执行类,通过它我们可以方便地进行串行、并行、组合和转换异步任务。它能够以一种非常灵活的方式处理异步操作的结果,包括成功的结果、异常和取消等情况。接下来,我们就详细了解一下这个类。

2 具体方法及使用

在正常的业务代码开发中,如果我们需要使用子线程处理数据通常需要使用线程池,但手动创建线程池很麻烦,而且还需要注意销毁。在 JDK8 中 CompletableFuture就很好的替我们解决了这个问题。下面我们来看一下如何使用这个类。

实例化

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不会,此外在进行实例化时可以指定线程池

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);

获取结果

public T    get()
public T    get(long timeout, TimeUnit unit)
public T    getNow(T valueIfAbsent)
public T    join()

get和join方法都是阻塞当前线程,直到获取接口 但join方法不会显式的抛出异常,更适合在流式编程中使用,get方法还可以指定等待时间,超时抛出异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {   
 // 执行具有返回值的任务    
 return "灵墨AI探索室";});
 future.get();
 //需处理异常
future.join();
future.get(1, TimeUnit.MINUTES);//get方法可以指定等待时间

下一步处理

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举一个例子

CompletableFuture<Score> future = CompletableFuture.supplyAsync(() -> {   
        // 获取学生信息           
 return StudentService.getStudent(id);       
  }).thenApply(student -> {   
           // 再根据学生信息获取考试分数          
    return ScoreServcie.getScore(student);      
 });

异常处理

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 中有两个属性值值得关注

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {  
  volatile Object result;           
  volatile Completion stack;   
 }
复制

其中result 用来保存future的结果,stack是利用链表实现的栈,记录了这个future的后续动作,在使用CompletableFuture 时,每次调用都会生成一个新的CompletableFuture 这些future就是通过stack记录的信息串联起来的,我们来举一个例子

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核数是有限的,如果使用异步的地方过多,最终也会导致阻塞,各位小伙伴在使用时也多加注意,希望各位小伙伴能点个关注,这对我们是一种很大的鼓励。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。