CompletableFuture 使用方法及原理

一、背景

目前一些小伙伴还在使用线程池 + Future的方式,不太了解CompletableFuture,也不习惯于使用更加优雅的CompletableFuture。因此,主要是介绍CompletableFuture, 后续在项目中多多使用CompletableFuture, 提升代码的优雅度,增加开发效率。

二、相关知识

2.1 基本介绍

CompletableFuture是Java 8引入的一个异步编程工具,它实现了Future接口,可以用于表示一个尚未完成的操作。与Java中的Future不同,CompletableFuture提供了更丰富的方法来处理异步任务的结果和异常。CompletableFuture 解决了 Future 的一些问题,同时提供了一些更加强大的功能:

  1. 多个异步计算结果的组合:Future 之间是相互独立的,无法将多个 Future 组合成一个复杂异步计算。CompletableFuture 提供了一些方法,如 thenApply 、thenCompose 等,可以方便地将多个异步计算组合起来;
  2. 回调函数:Future 只能通过 Future.get() 方法来阻塞等待异步计算结果,这种方式会影响程序性能。CompletableFuture 可以通过 thenApply 、thenAccept 等方法,在异步计算完成后执行回调函数,提高了程序的响应性能;
  3. 异常处理:CompletableFuture提供了丰富的异常处理方法,可以在异步任务发生异常时进行捕获和处理;
  4. 链式调用:CompletableFuture支持链式调用,可以将多个异步操作组合在一起,形成一个异步任务链。这使得代码更加简洁易读。

2.2 各种场景

任务类型 解决方案
简单并行任务 线程池 + FutureTask
聚合任务 CompletableFuture
批量并行任务 CompletionService
分治任务 Fork/Join

三、CompletableFuture的具体使用

3.1 从顶层认识CompletableFuture

从任务关系的角度来认识CompletableFuture:

从阶段维度认识CompletableFuture

3.2 CompletableFuture提供的具体实现

3.2.1 创建CompletableFuture

提供了四种方法,如下所示:

java 复制代码
// 使用默认线程池
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    // 执行任务
    System.out.println("任务1在默认线程池中执行");
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    // 执行任务
    System.out.println("任务2在默认线程池中执行");
    return "任务2执行结果";
});

// 指定线程池
Executor executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
    // 执行任务
    System.out.println("任务3在指定线程池中执行");
}, executor);

CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
    // 执行任务
    System.out.println("任务4在指定线程池中执行");
    return "任务4执行结果";
}, executor);

3.2.2 执行CompletableFuture

从串行角度看:主要是 thenApply、thenAccept、thenRun 和 thenCompose 这四个系列的接口

java 复制代码
CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);

从并行的And角度看:主要是 thenCombine、thenAcceptBoth 和 runAfterBoth 系列的接口

java 复制代码
CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);

从并行的OR角度看:主要是 applyToEither、acceptEither 和 runAfterEither 系列的接口

java 复制代码
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);

3.2.3 执行结束处理

主要是对正常结果的处理和异常结果的处理。上述提供的方法不允许抛出可检查型异常,但无法限制抛出运行时异常。CompletableFuture提供了优雅的方案来处理该问题:

java 复制代码
CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);
# 四、CompletableFuture原理
## 4.1 核心的类
**CompletableFuture类**定义:
```java
public class CompletableFuture<T>
extends Object
implements java.util.concurrent.Future<T>, java.util.concurrent.CompletionStage<T> {
	volatile Object result;       // Either the result or boxed AltResult
	volatile Completion stack;    // Top of Treiber stack of dependent actions
}

一个是Completion对象stack,这是一个CAS实现的无锁并发栈,每个链式调用的任务会被压入这个栈。另一个是Object对象result,这是当前CompletableFuture的结果

CompletionStage接口:CompletableStage用来表示异步过程中的一个阶段,它可以在另一个CompletableStage完成时做一些操作或计算,此接口中定义了一些基本的行为,通过这些行为组合可以简洁的描述非常复杂的任务。查看官方注释文档:CompletionStage是一个可能执行异步计算的"阶段",这个阶段会在另一个CompletionStage完成时调用去执行动作或者计算,一个CompletionStage会以正常完成或者中断的形式"完成",并且它的"完成"会触发其他依赖的CompletionStage。CompletionStage 接口的方法一般都返回新的CompletionStage,因此构成了链式的调用。

java 复制代码
public interface CompletionStage<T> {
    public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
    public <U> CompletionStage<U> thenApplyAsync
        (Function<? super T,? extends U> fn);
}

Comletion类定义:管理当前CompletableFuture的关系以及执行器

java 复制代码
/** A Completion with a source, dependent, and executor. */
@SuppressWarnings("serial")
abstract static class UniCompletion<T,V> extends Completion {
    Executor executor;                 // executor to use (null if none)
    CompletableFuture<V> dep;          // the dependent to complete 真正控制当前执行的流程
    CompletableFuture<T> src;          // source for action 触发它执行的源头
}

/* ------------- Base Completion classes and operations -------------- */

@SuppressWarnings("serial")
abstract static class Completion extends ForkJoinTask<Void>
    implements Runnable, AsynchronousCompletionTask {
    volatile Completion next;   // 下一个要执行的任务
}

4.2 原理分析

4.2.1 示例1

java 复制代码
@Test
public void test9() throws ExecutionException, InterruptedException {
    CompletableFuture<String> base = new CompletableFuture<>();
    CompletableFuture<String> future = base.thenApply(s -> s + " 2").thenApply(s -> s + " 3");
    base.complete("1");
    System.out.println(future.get());
}
结果:1 2 3

可以看到:链式调用,依赖于dep,组成一条单链表,按照从首到尾的顺序执行。

4.2.2 示例2

java 复制代码
@Test
public void test10() {
    CompletableFuture<String> base = new CompletableFuture<>();
    CompletableFuture<String> future =
            base.thenApply(
                    s -> {
                        log.info("e");
                        return s + " 2";
                    });
    base.thenAccept(s -> log.info("c")).thenAccept(aVoid -> log.info("d"));
    base.thenAccept(s -> log.info("a")).thenAccept(aVoid -> log.info("b"));

    base.complete("1");
}
结果:
22:24:26.725 [main] INFO com.example.hellodello.thread.CompletableFutureTest - a
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - b
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - c
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - d
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - e

4.2.3 通用调用链路

整体采用:CAS链式栈 + 单链表的链路设计

五、人物志

复制代码
编程不识Doug Lea, 写尽Java也枉然 -- 来自微博

如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea。这个鼻梁挂着眼镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego分校计算机科学系的老大爷。他专门研究并发编程和并发数据结构的设计。

说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变革,他都间接或直接的扮演了举足轻重的角色。

  • 贡献1:一次是由JDK 1.1到JDK 1.2,JDK1.2很重要的一项新创举就是Collections,其Collections的概念可以说承袭自Doug Lea于1995年发布的第一个被广泛应用的collections;
  • 贡献2:一次是2004年所推出的Tiger。Tiger广纳了15项JSRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166。JSR-166是来自于Doug编写的util.concurrent包

具体的示例:

java 复制代码
-- 杰作HashMap;
-- java.util.concurrent 包;
-- AQS奠基者

社会评价:Doug是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为给了别人就减少了,知识的分享更能激荡出不一样的火花。《Effective JAVA》这本Java经典之作的作者Joshua Bloch便在书中特别感谢Doug Lea是此书中许多构想的共鸣板,感谢Doug Lea大方分享丰富而又宝贵的知识。

相关推荐
超级大福宝3 分钟前
用买火车票的例子讲解Java反射的作用
java·开发语言·后端
程序员爱钓鱼3 分钟前
Go高性能缓冲IO详解: bufio包深度指南
后端·面试·go
熙胤15 分钟前
Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)
spring boot·后端·ui
tumeng071123 分钟前
springboot项目架构
spring boot·后端·架构
LES000LIE26 分钟前
Spring Cloud
后端·spring·spring cloud
mldlds1 小时前
Spring Boot应用关闭分析
java·spring boot·后端
zjjsctcdl1 小时前
Spring Boot与MyBatis
spring boot·后端·mybatis
tuyanfei1 小时前
Spring 简介
java·后端·spring
代码探秘者1 小时前
【大模型应用】2.RAG详细流程
java·开发语言·人工智能·后端·python