JUC之十一:CompletableFuture用法详解

news2024/12/27 0:35:04

JUC之十一:CompletableFuture用法详解

文章目录

  • JUC之十一:CompletableFuture用法详解
    • 一、前言
    • 二、CompletableFuture类简介
    • 三、功能分类
      • 3.1、获取结果
      • 3.2、依赖关系
      • 3.3、and聚合关系
      • 3.4、or聚合关系
      • 3.5、并行执行
      • 3.6、结果处理
    • 四、方法详解
      • 4.1、结果转换
      • 4.2、结果消费
      • 4.3、任务组合交互
    • 五、使用案例

一、前言

前面介绍了FutureTask,获取异步执行结果通过get的形式,而且会阻塞主线程,随着开发的越来越越复杂已经无法满足真正开发场景,我们试想一个例子:

我们再炒菜的时候,先洗菜,切菜,炒菜,盛菜,然后吃。。。。。。

如果还用原来的future来操作的话,需要每一个步骤get出前一个线程的结果之后才能继续执行,导致主线程阻塞,无法去干别的事情,如蒸米饭等,为了解决这一问题提出了CompletableFuture的异步编程方式。

FutureTask与CompletableFuture的区别:

1、FutureTask获取异步执行的结果通过get实现,会阻塞主线程 ,CompletableFuture执行完一个阶段之后自动通知下一个阶段开始执行,主线程可继续处理其他的事情。

2、FutureTask没有处理异常的方法,CompletableFuture可处理异步线程内的异常。

3、CompletableFuture可对多个任务进行任意的组合、并行、串行使用。

二、CompletableFuture类简介

继承关系如下所示:

继承关系

继承自Future接口以及CompletionStage接口,Future前面介绍FutureTask的时候已经介绍过。

CompletionStage接口定义了具体的异步编程的方法,执行某一阶段之后返回一个新的CompletionStage可继续执行下一阶段,CompletableFuture默认使用的线程池是ForkJoinPool.commonPool(),前面介绍过ForkJoinPool使用的是守护线程,所以注意主线程何时结束避免异步执行失败。

private static final Executor asyncPool = useCommonPool ?
    ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

三、功能分类

3.1、获取结果

  • get:实现自Future接口,获取结果,会阻塞主线程。
  • getTime:实现自Future接口,在指定时间内线程完成获取结果,若未完成则抛出异常。
  • getNow:立即获取结果,若任务完成则返回任务值,否则返回指定的值。
  • join:与get相同作用,区别在于返回的是非检查异常,外部可处理也可不处理,而get必须处理。
  • complete:任务是否完成,若完成返回true,未完成则直接将任务结果设置为指定的值。
        //如果已经完成了返回true,否则返回false,并设置为该值
        boolean complete = future.complete(10);
        System.out.println(complete);
//        获取结果
        Integer get = future.get();
//        获取结果,如果超时直接抛出异常
        Integer getTime = future.get(1, TimeUnit.DAYS);
//        直接获取结果,如果任务没完成,则返回指定的值
        Integer getNow = future.getNow(20);
//        获取结果,与get区别是返回一个(unchecked)CompletionException异常,可处理也可不处理,而get返回检查异常,需要具体的处理
        Integer join = future.join();

3.2、依赖关系

  • thenApply: 拿着上一阶段的执行结果,执行函数,返回结果。
  • thenCompose: 拿着上一阶段的结果,执行新的任务,并返回第二个任务的结果。
  • thenAccept: 消费函数,拿着上一阶段的结果,执行函数。
  • thenRun: 任务执行完直接执行函数,不关心任务结果,无返回值。

3.3、and聚合关系

  • thenCombine: 合并两个任务,两个任务的结果执行函数,返回函数执行后的结果。

  • thenAcceptBoth: 合并两个任务,两个任务的结果执行函数,无返回值。

  • runAfterBoth: 执行完两个任务之后,执行函数,不关心前两个任务的结果。

3.4、or聚合关系

  • applyToEither: 两个线程任务相比较,先获得执行结果的,执行后续的函数返回结果。

  • acceptEither:两个线程任务相比较,先获得执行结果的,执行后续的函数,无返回值。

  • runAfterEither: 两个线程任务任何一个执行完,执行函数,不关心任务结果,无返回值。

3.5、并行执行

  • anyOf: 任何一个线程任务执行结束,返回结果。

  • allOf: 等待所有线程执行结束,无返回值。

3.6、结果处理

  • whenComplete: 任务执行结束,对结果以及异常进行处理,返回同一种类型。

  • exceptionally: 任务执行结束,对异常进行处理。

  • handle: 任务执行结束,对结果以及异常进行处理,返回任意类型的结果。

四、方法详解

CompletionStage接口同一种类型的方法一般是三个

如:

//同步方法
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
//异步方法
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
//异步方法,不使用默认的线程池,指定线程池。
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);

后面只做方法介绍,不区分同步以及异步。

4.1、结果转换

thenApply:

获取上一任务的执行结果,执行当前函数,返回函数执行结果。

入参:

Function<? super T,? extends U> fn: 函数的入参是前一个任务的结果, 出参是函数的执行结果。

方法:
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);

使用示例:
CompletableFuture<String> thenApply = CompletableFuture
    .supplyAsync(() -> 10)
    .thenApply(x -> "衬衫的价格是:" + x);

thenCompose:

获取上一任务的执行结果,开启新的 CompletableFuture 进行计算。

方法:
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);

使用实例:
CompletableFuture<String> thenCompose = CompletableFuture
      .supplyAsync(() -> 10)
      .thenCompose(x -> CompletableFuture.supplyAsync(() -> "衬衫的价格是:" + x));

thenApply thenCompose 的区别:

  • thenApply转换的是泛型中的类型,返回的是同一个CompletableFuture
  • thenCompose将内部的CompletableFuture调用展开来并使用上一个CompletableFutre调用的结果在下一步的CompletableFuture调用中进行运算,是生成一个新的CompletableFuture

thenCombine:

组合两个任务,对两个任务结果进行计算,并返回计算结果。

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,
                                              BiFunction<? super T,? super U,? extends V> fn)
 
 使用实例:
        CompletableFuture<String> thenCombine = CompletableFuture
                .supplyAsync(() ->10)
                .thenCombine(CompletableFuture.supplyAsync(() -> 0.8),
                             (r1, r2) -> "衬衫打八折的价格是:" + r1 * r2);

4.2、结果消费

thenAccept:

获取上一任务的执行结果,进行消费,无返回值。

方法:
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);

使用实例:
CompletableFuture<Void> thenAccept = CompletableFuture
      .supplyAsync(() -> 10)
      .thenAccept(x -> System.out.println("衬衫的价格是" + x));

thenRun:

不考虑上一任务的执行结果,执行Runnable函数,无返回值。

方法:
public CompletableFuture<Void> thenRun(Runnable action);

使用实例:
CompletableFuture<Void> thenRun = CompletableFuture
       .supplyAsync(() -> 10)
       .thenRun(() -> System.out.println("执行thenRun"));

thenAcceptBoth:

获取两个任务的执行结果,进行消费。

public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action) 
    
使用实例:
CompletableFuture<Void> thenAcceptBoth = CompletableFuture
    .supplyAsync(() -> 10)
    .thenAcceptBoth(CompletableFuture.supplyAsync(() -> 0.8), (r1, r2) -> {
        System.out.println("衬衫打八折的价格是:" + r1 * r2);
    });

runAfterBoth:

执行完两个任务后,执行runnable函数,不关心前两个任务的执行结果,无返回值。

public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action)
    
使用实例:
CompletableFuture<Void> runAfterBoth = CompletableFuture
    .supplyAsync(() -> 10)
    .runAfterBoth(CompletableFuture.supplyAsync(() -> 0.8), () -> {
        System.out.println("runAfterBoth正在执行计算函数");
    });

4.3、任务组合交互

applyToEither:

两个任务谁先执行结束,就做为后续Function函数的计算入参。

public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other,
                                              Function<? super T, U> fn)
    
  使用实例:
        CompletableFuture<Integer> applyToEither = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return 10;
                })
                .applyToEither(CompletableFuture.supplyAsync(() -> {
//                    try {
//                        Thread.sleep(2000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    return 20;
                }), r -> r);
    

acceptEither:

两个任务谁先执行结束,就进行后续的消费操作。

public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other,
                                            Consumer<? super T> action)
    
使用实例:
        CompletableFuture<Void> acceptEither = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return 10;
                })
                .acceptEither(CompletableFuture.supplyAsync(() -> {
//                    try {
//                        Thread.sleep(2000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    return 20;
                }), () -> System.out.println("acceptEither执行结束啦"));

runAfterEither:

两个任务任意一个执行结束,执行Runnable函数。

public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action)
    
        CompletableFuture<Void> runAfterEither = CompletableFuture
                .supplyAsync(() -> 10)
    		    .runAfterEither(CompletableFuture.supplyAsync(() -> 20), 
                                  () -> System.out.println("runAfterEither执行结束啦"));

anyOf:

任何一个线程任务执行结束,返回结果。

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
    
CompletableFuture<Object> anyOf = CompletableFuture
                .anyOf(CompletableFuture.supplyAsync(() -> 100),
                        CompletableFuture.supplyAsync(() -> "200"));

allOf:

等待所有线程执行结束,无返回值。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)        

CompletableFuture<Void> allOf = CompletableFuture
                .allOf(CompletableFuture.supplyAsync(() -> 100),
                        CompletableFuture.supplyAsync(() -> "200"));

五、使用案例

实现最优的“烧水泡茶”程序

流程图

对于烧水泡茶这个程序,一种最优的分工方案:用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶、烧开水、泡茶这三道工序,T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序。

public class CompletableFutureTest {

    public static void main(String[] args) {

        //任务1:洗水壶->烧开水
        CompletableFuture<Void> f1 = CompletableFuture
            .runAsync(() -> {
                System.out.println("T1:洗水壶...");
                sleep(1, TimeUnit.SECONDS);

                System.out.println("T1:烧开水...");
                sleep(15, TimeUnit.SECONDS);
            });
        //任务2:洗茶壶->洗茶杯->拿茶叶
        CompletableFuture<String> f2 = CompletableFuture
            .supplyAsync(() -> {
                System.out.println("T2:洗茶壶...");
                sleep(1, TimeUnit.SECONDS);

                System.out.println("T2:洗茶杯...");
                sleep(2, TimeUnit.SECONDS);

                System.out.println("T2:拿茶叶...");
                sleep(1, TimeUnit.SECONDS);
                return "龙井";
            });
        //任务3:任务1和任务2完成后执行:泡茶
        CompletableFuture<String> f3 = f1.thenCombine(f2, (__, tf) -> {
            System.out.println("T1:拿到茶叶:" + tf);
            System.out.println("T1:泡茶...");
            return "上茶:" + tf;
        });
        //等待任务3执行结果
        System.out.println(f3.join());
    }

    static void sleep(int t, TimeUnit u){
        try {
            u.sleep(t);
        } catch (InterruptedException e) {
        }
    }
}

参考链接:

https://blog.csdn.net/sermonlizhi/article/details/123356877

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaScript】ES6新特性(5)

16. Promise Promise 是异步编程的一种解决方案&#xff0c;比传统的解决方案回调函数, 更合理和更强大 ES6 将其写进了语言标准&#xff0c;统一了用法&#xff0c;原生提供了Promise对象 指定回调函数方式更灵活易懂解决异步 回调地狱 的问题 16.1 回调地狱 当一个回调函数…

Linux 进程管理与调度

一、前言 本篇文章是对Liunx的进程管理调度的实验过程记录&#xff0c;读者可以通过这篇文章加深对Liunx中进程管理和调度的理解&#xff0c;本文所使用的操作系统为RHEL 8.0版本&#xff0c;为在Vmware workstation中运行的虚拟机。 二、实验目的 ① 掌握linux进程相关操作 ②…

RabbitMQ学习笔记9 TTL死信队列+延迟队列实战

我们去新建一个交换机&#xff1a; 然后我们再用这种方法直接创建一个队列&#xff1a; 点击bind这样我们的交换机和队列就绑定到一起了。 然后我们要新建普通队列&#xff0c;设置过期时间&#xff0c;指定死信交换机。 发布一条消息。 它会把队列传递到死信队列中去。

Openlayers实战:overlay上播放视频

在企业或者城市等的宣传上,视频能很好的传达出一些信息。 那么在openlayer中如何展示视频呢, 可以通过overlay的方式,点击某一个点,弹出overlay层,展示出视频,并自动播放。下面的实战就能够达到这种效果。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还…

记忆——记忆宫殿——数字编码

数字编码 数字编码相对简单&#xff0c;与乘法口诀的工作量类似。如果想要达到高手的水平&#xff0c;熟练程度也需要达到乘法口诀的程度。 数字编码的方式是直接创造出数字与形象的唯一的对应关系&#xff0c;然后熟记。 0-9&#xff0c;00-99是什么 最常用的编码是将0-9和…

【C++11】lambda表达式 的定义、性质和用法

定义 Lambda表达式是C11引入的一种函数对象的匿名表示方法。它可以用于定义轻量级的、临时的、内联的函数对象&#xff0c;通常用于函数式编程的场景。 语法结构 Lambda表达式的 基本语法 如下&#xff1a; lambda表达式书写格式&#xff1a;[capture-list] (parameters) m…

java8 新特性 —— Lambda 表达式

我们来看一段老版本的示例代码&#xff0c;其对一个含有字符串的集合进行排序&#xff1a;&#xff08;示例代码创建了一个匿名内部类作为入参。&#xff09; // Collections 工具类提供了静态方法 sort 方法&#xff0c;入参是一个 List 集合&#xff0c;和一个 Comparator 比…

C++栈和队列(详解+刷题)

&#x1f442; 爱要坦荡荡 - 萧潇 - 单曲 - 网易云音乐 &#x1f442; 武侯祠 - 闫东炜 - 单曲 - 网易云音乐 &#x1f442; You Are My Sunshine - Angelika Vee - 单曲 - 网易云音乐 (๑•̀ㅂ•́)و✧ O(∩_∩)O (ง •_•)ง (&#xff5e;﹃&#xff5e;)~zZ …

微鳄三六五软件,企业实现数字化转型仅需2万

关键词&#xff1a;OA办公系统、知识管理系统、群晖NAS 编者按&#xff1a; 疫情对全球经济有着巨大冲击&#xff0c;有调查表明80%的中小企业营收下滑&#xff0c;有30%的中小企业营收减半。天翎特此推出万元即可实现数字化管理方案&#xff0c;为企业提供办公管理、项目管理、…

Canvas.arcTo() 的使用,画一条带圆角的线段,画一个思维导图一基础

Canvas.arcTo() 的使用&#xff0c;画一条带圆角的线段&#xff0c;画一个思维导图一基础 一、方法定义 canvas.arcTo 这个方法还是比较难理解的。因为它不是直观的绘制路径&#xff0c;而是间接的。 它的参数是这样的。 canvas.arcTo(ax,ay,bx,by,radius)它是由两个点和一个…

密码学学习笔记(十):Digital Signatures - 数字签名1

什么是数字签名&#xff1f; 想象一下一下情景&#xff1a; Alice生成两个密钥&#xff1a;一个公钥&#x1d443;&#x1d43e;&#x1d434; & 对应的密钥&#x1d446;&#x1d43e;&#x1d434;, 发布公钥&#xff0c;保留密钥然后Alice使用&#x1d446;&#x1d4…

ARG DEBIAN_FRONTEND=noninteractive作用说明

使用 在Dockerfile中使用ARG指令定义变量并为其指定一个默认值。ARG指令用于在构建过程中传递变量的值。 对于DEBIAN_FRONTENDnoninteractive&#xff0c;它定义了一个名为DEBIAN_FRONTEND的变量&#xff0c;并将其默认值设置为noninteractive。在这个上下文中&#xff0c;no…

【uniapp开发h5】点击复制微信号并自动打开微信应用,类似可以唤起淘宝、知乎应用等

效果展示&#xff1a; 准备工作&#xff1a; 引用jquery.js 和 clipboard.min.js jQuery就不用多说了&#xff0c;而clipboard.min.js 是一个 JavaScript 库&#xff0c;用于实现网页上的复制到剪贴板功能。它提供了一种简单的方式来处理复制操作&#xff0c;无需使用浏览器原…

Ubuntu 包管理的 20 个“apt-get”命令

动动发财的小手&#xff0c;点个赞吧&#xff01; 在引入 apt 命令之前&#xff0c;apt-get 命令是基于 Debian 的 Linux 发行版中使用的主要包管理命令。 使用 apt-get 命令&#xff0c;您可以在系统上安装、删除、升级、搜索和管理软件包。然而&#xff0c;从 Ubuntu 16.04 和…

22运动估计(matlab程序)

1.简述 实验目的 熟悉运动估计的块匹配&#xff08;BMA&#xff09;算法原理&#xff0c;编程实现全搜索算法&#xff08;三步搜索或钻石搜索算法&#xff09;&#xff0c;了解运动估计在混合编码器中的作用。 实验内容 1&#xff09;编写全搜索算法函数&#xff0c;将运动矢量…

使用Llama.cpp在CPU上快速的运行LLM

大型语言模型(llm)正变得越来越流行&#xff0c;但是它需要很多的资源&#xff0c;尤其时GPU。在这篇文章中&#xff0c;我们将介绍如何使用Python中的llama.cpp库在高性能的cpu上运行llm。 大型语言模型(llm)正变得越来越流行&#xff0c;但是它们的运行在计算上是非常消耗资源…

MongoDB教程-5

复制是跨多个服务器同步数据的过程。复制在不同的数据库服务器上提供数据的多个副本&#xff0c;从而提供冗余并提高数据可用性。复制可防止数据库丢失单个服务器。复制还允许您从硬件故障和服务中断中恢复。通过增加数据拷贝&#xff0c;您可以将其中一个用于灾难恢复、报告或…

Spring系列4 -- Bean的作用域和生命周期

目录 1. 案例 2. 作用域定义 2.1 Bean的6种作用域 2.2 设置作用域 3. Sring的执行流程 4. Bean的生命周期 思考: 为什么不是先进行初始化然后再进行设置属性呢? 1. 案例 假设现在有⼀个公共的 Bean&#xff0c;提供给 A ⽤户和 B ⽤户使⽤&#xff0c;然⽽在使⽤的途中…

【面试】Hbase

逻辑模型 1 NameSpace 命名空间&#xff0c;类似于关系型数据库的database概念&#xff0c;每个命名空间下有多个表。Hbase有两个自带的命名空间,分别是hbase和default, hbase中存放的是HBase内置的表, default表是用户默认使用的命名空间。 2 Region 类似于关系型数据库的表…

综合 案例

案例1&#xff1a;淘宝焦点图布局 基本结构 1.大盒子我们类名为: tb-pro淘宝广告 2.里面先放一张图片 3.左右两个按钮用链接。左箭头prev 右箭头 next 4.底侧小圆点用ul 类名为pro-nav 注意&#xff1a; 1.如果一个盒子既有left属性也有right属性&#xff0c;则默认会执行lef…