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大方分享丰富而又宝贵的知识。

相关推荐
Estar.Lee1 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪3 分钟前
Django:从入门到精通
后端·python·django
一个小坑货3 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet277 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
uhakadotcom30 分钟前
如何实现一个基于CLI终端的AI 聊天机器人?
后端
Iced_Sheep1 小时前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
凡人的AI工具箱2 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang