CompletableFuture总结和实践

news2024/11/24 19:41:38

CompletableFuture被设计在Java中进行异步编程。异步编程意味着在主线程之外创建一个独立的线程,与主线程分隔开,并在上面运行一个非阻塞的任务,然后通知主线程进展,成功或者失败。

一、概述

1.CompletableFuture和Future的区别?

CompletableFuture和Future出现的原因是继承Thread或者实现Runnable接口的异步线程没有返回值,需要返回值的异步线程可以通过CompletableFuture和Future来创建。

CompletableFuture和Future都可以获取到异步线程的返回值,但是Future只能通过get()方法阻塞式获取,CompletableFuture由于实现了CompletionStage接口,可以通过丰富的异步回调方式来执行后续的操作,同时还能对多个任务按照先后顺序进行任务编排。

2.特性

CompletableFuture的作用主要体现在:(1)异步回调;(2)任务编排;

3.使用场景

  1. 执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度
  2. 使用CompletableFuture类,它提供了异常管理的机制,让你有机会抛出、管理异步任务执行种发生的异常
  3. 如果这些异步任务之间相互独立,或者他们之间的的某一些的结果是另一些的输入,你可以讲这些异步任务构造或合并成一个

二、原理

CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。

image-20230819001933921

Future接口主要提供get()和join()方法,可以获取任务的返回值,同时也会阻塞调用线程。

CompletionStage接口提供了丰富的执行异步调用和任务处理的接口,任务之间可以分为存在时序关系,包括串行关系、并行关系和汇聚关系等。

三、实践

1.创建任务和获取结果

(1)runAsync创建任务

通过runAsync()方法创建的异步任务没有返回值,其中有有runAsync(Runnable runnable)和runAsync(Runnable runnable, Executor executor)两个重载方法,后者比前者多一个Executor executor,即可以传入自定义的线程池,如果没传即用默认线程池(ForkJoinPool.commonPool())。

ExecutorService executor = Executors.newFixedThreadPool(3);

CompletableFuture<Void> f1 = CompletableFuture.runAsync(new Runnable() {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("f1 start");
  }
}, executor);

(2)supplyAsync创建任务

通过supplyAsync()方法创建的异步任务有返回值,其中有有supplyAsync(Runnable runnable)和supplyAsync(Runnable runnable, Executor executor)两个重载方法,后者比前者多一个Executor executor,即可以传入自定义的线程池,如果没传即用默认线程池(ForkJoinPool.commonPool())。

CompletableFuture<String> f2 = CompletableFuture.supplyAsync(new Supplier<String>() {
    @Override
    public String get() {
        System.out.println("f2 start");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "f2 return";
    }
}, executor);

总结:

runAsync创建的异步任务没有返回值,supplyAsync创建的异步任务有返回值。

(3)获取结果和结束任务

// 如果完成则返回结果,否则就抛出具体的异常
public T    get()
// 最大时间等待返回结果,否则就抛出具体异常
public T    get(long timeout, TimeUnit unit)
// 如果完成则返回结果值(或抛出任何遇到的异常),否则返回默认值。
public T    getNow(T valueIfAbsent)
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T    join()
//如果任务还未完成,直接给他返回值置为value
public boolean complete(T value) 

2.异步回调

(4)whenComplete和whenCompleteAsync

whenComplete()方法是当某个任务执行完成后执行的回调方法。该方法会将该任务的执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和是该任务的返回值,如果该任务正常执行,则get方法返回执行结果,如果是执行异常,则get方法抛出异常。该任务抛出的异常,一般用exceptionally()处理,其入参是异常Throwable throwable,可以有返回值。

whenComplete和whenCompleteAsync和区别在于whenComplete是在当前线程中执行该回调任务,whenCompleteAsync是会另启动一个线程来执行该回调任务,默认情况下是使用ForkJoinPool.commonPool()线程池中的线程,也可以设置自定义线程池。后面以-Async为后缀的方法也都是这样的区别。

CompletableFuture<String> f7 = f2.whenCompleteAsync((result, e) -> {
            System.out.println("f7 start");
            if (1 == 1) {
                throw new IllegalStateException("f7 IllegalStateException");
            }
        }
).exceptionally(new Function<Throwable, String>() {
    @Override
    public String apply(Throwable throwable) {
        System.out.println("f8 exception: " + throwable);
        return "f8 return";
    }
});

(5)handle和handleAsync

handle方法也是当某个任务执行完成后执行的回调方法,整体功能和whenComplete方法差不多,但是handleAsync方法会有返回值,handleAsync()可以传入自定义线程池。

CompletableFuture<String> f5 = f2.handleAsync(new BiFunction<String, Throwable, String>() {
    @Override
    public String apply(String s, Throwable throwable) {
        System.out.println("f5 start");
        if (1 == 1) {
            throw new IllegalStateException("f5 IllegalStateException");
        }
        return "f5 return";
    }

}, executor).exceptionally(new Function<Throwable, String>() {
    @Override
    public String apply(Throwable throwable) {
        System.out.println("f6 exception: " + throwable);
        return "f6 return";
    }
});

总结:

whenComplete和handle的区别?

whenComplete的回调方法没有返回值,handle方法有返回值?

(6)thenApply和thenApplyAsync

thenApply 表示某个任务执行完成后执行回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,带有返回值。其中

CompletableFuture<String> f3 = f2.thenApplyAsync(new Function<String, String>() {
    @Override
    public String apply(String s) {
        System.out.println(s + ",f3 start");
        return "f3 return";
    }
}, executor);

(7)thenAccept和thenAcceptAsync

thenAccep表示某个任务执行完成后执行的回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,无返回值。

CompletableFuture<Void> f3 = f2.thenAcceptAsync(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println("f3 start");
    }
}, executor);

(8)thenRun和thenRunAsync

thenRun表示某个任务执行完成后执行的动作,即回调方法,无入参,无返回值。

CompletableFuture<Void> f4 = f2.thenRunAsync(new Runnable() {
    @Override
    public void run() {
        System.out.println("f4 start");
    }
}, executor);

异步回调方法总结:

方法入参返回值异常处理
whenComplete有入参无返回值能抛出异常
handle有入参有返回值能抛出异常
thenApply有入参有返回值不能抛出异常
thenAccept有入参无返回值不能抛出异常
thenRun无入参无返回值不能抛出异常

3.任务编排

(9)thenCombine、thenAcceptBoth 和runAfterBoth

这三个方法都是将两个CompletableFuture组合起来处理,只有两个任务都正常完成时,才进行下阶段任务。可以理解为为:两个任务AND汇聚后才能执行。

区别:thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。

        ExecutorService executor = Executors.newFixedThreadPool(3);

        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("f1 running");
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "f1 return";
            }
        }, executor);

        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("f2 running");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "f2 return";
            }
        }, executor);

        CompletableFuture<String> f30 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("f30 running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "f30 return";
            }
        }, executor);


        CompletableFuture<String> f3 = f1.thenCombine(f2, new BiFunction<String, String, String>() {
            @Override
            public String apply(String s, String s2) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(s + "," + s2 + "," + "f3 running");
                return "f3 ruturn";
            }
        });

        CompletableFuture<Void> f4 = f1.thenAcceptBoth(f2, new BiConsumer<String, String>() {
            @Override
            public void accept(String s, String s2) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(s + "," + s2 + "," + "f4 running");
            }
        });

(10)applyToEither、acceptEither和runAfterEither

这三个方法和上面一样也是将两个CompletableFuture组合起来处理,当有一个任务正常完成时,就会进行下阶段任务。可以理解为:两个任务OR汇聚后才能执行。

区别:applyToEither会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值;acceptEither同样将已经完成任务的执行结果作为方法入参,但是无返回值;runAfterEither没有入参,也没有返回值。

CompletableFuture<String> f6 = f1.applyToEither(f2, new Function<String, String>() {
    @Override
    public String apply(String s) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(s + "," + "f6 running");
        return "return f6";
    }

});


CompletableFuture<Void> f7 = f1.acceptEither(f2, new Consumer<String>() {
    @Override
    public void accept(String s) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(s + "," + "f7 running");
    }
});

CompletableFuture<Void> f8 = f1.runAfterEither(f2, new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("f7 running");
    }
});

(11)allOf / anyOf

allOf和anyOf都是CompletableFuture的方法,他们针对的都是多任务汇聚后才能执行的逻辑,可以理解为多任务AND/OR汇聚后模式。

allOf:CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。

anyOf :CompletableFuture是多个任务只要有一个任务执行完成,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果。

CompletableFuture<Void> f9 = CompletableFuture.allOf(f1, f2, f30);
CompletableFuture<Object> f10 = CompletableFuture.anyOf(f1, f2, f30);

任务编排方法总结:

类型方法入参返回值描述
两个任务AND汇聚thenCombine有入参有返回值
两个任务AND汇聚thenAcceptBoth有入数无返回值
两个任务AND汇聚runAfterBoth无入参无返回值
两个任务OR汇聚applyToEither有入参有返回值
两个任务OR汇聚acceptEither有入参无返回值
两个任务OR汇聚runAfterEither无入参无返回值
多任务AND汇聚后模式allOf全部执行
多任务OR汇聚后模式anyOf至少一个执行

总结以上方法:

  1. 以Async结尾的方法,都是异步方法,对应的没有Async则是同步方法,一般都是一个异步方法对应一个同步方法;以Async后缀结尾的方法,都有两个重载的方法,一个是使用内容的forkjoin线程池,一种是使用自定义线程池;
  2. 以Apply开头或者结尾的方法,入口有参数,有返回值;
  3. 以supply开头的方法,入口也是没有参数的,但是有返回值;
  4. 以Accept开头或者结尾的方法,入口参数是有参数,但是没有返回值;
  5. 以run开头的方法,其入口参数一定是无参的,并且没有返回值,类似于执行Runnable方法。
  6. 和异步方法相比,任务编排方法多数带有-Both或-Either的后缀,-Both表示需要执行全部任务,-Either表示至少执行一个任务。

4.代码实现

参考资料

  1. CompletableFuture使用详解(全网看这一篇就行):https://blog.csdn.net/zsx_xiaoxin/article/details/123898171 (主要参考)

  2. CompletableFuture使用大全,简单易懂:https://juejin.cn/post/6844904195162636295

  3. CompletableFuture用法详解:https://zhuanlan.zhihu.com/p/344431341

  4. 并发编程系列-CompletableFuture:https://zhuanlan.zhihu.com/p/650700731

  5. Java CompletableFuture实现多线程异步编排:https://juejin.cn/post/7140244126679138312

  6. 使用CompletableFuture:https://www.liaoxuefeng.com/wiki/1252599548343744/1306581182447650

  7. 并发编程 - CompletableFuture 解析 | 京东物流技术团队:https://zhuanlan.zhihu.com/p/646472720

本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

Centos7查看磁盘和CUP统计信息iostat命令

Centos7查看磁盘和CUP统计信息iostat命令 Centos7内存高|查看占用内存命令 docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六…

Interlij IDEA 运行 ruoyi 后端项目。错误: 找不到或无法加载主类 com.ruoyi.auth.RuoYiAuthApplication

错误: 找不到或无法加载主类 com.ruoyi.auth.RuoYiAuthApplication 用了 IDEA运行&#xff0c;参考以下issue删除.idea目录也没有用 (官方文档写是用Eclipse运行&#xff09; 错误: 找不到或无法加载主类 com.ruoyi.auth.RuoYiAuthApplication Issue #I48N2X 若依/RuoYi-C…

Seaborn数据可视化(二)

目录 1.Seaborn风格设置 1.1 主题设置 1.2 轴线设置 1.3 移除轴线 1.4 使用字典传递函数 2.设置绘图元素比例 2.1 设置绘图元素比例paper 2.2 设置绘图元素比例poster 2.3 设置绘图元素比例notebook Seaborn将Matplotlib的参数划分为两个独立的组合&#xff0c;第一组用于…

关于Coursera网站视频无法观看

文章目录 前言找Ip 改hosts验证 前言 众所周知&#xff0c;coursera是很不错的学习网站&#xff0c;但由于国内访问限制&#xff0c;导致我的学习之路举步维艰 在科学上网彻底崩盘后&#xff0c;终于断了我的学习热情&#xff08;真的很想骂人&#xff09; 网站只能登入&#…

Qt6之如何为QDialog添加最大化和最小化按钮

在QDialog构造函数中添加以下几行代码&#xff1a; // 设置窗体最大化和最小化Qt::WindowFlags windowFlag Qt::Dialog;windowFlag | Qt::WindowMinimizeButtonHint;windowFlag | Qt::WindowMaximizeButtonHint;windowFlag …

No115.精选前端面试题,享受每天的挑战和学习

文章目录 变量提升和函数提升的顺序Event Loop封装 FetchAPI&#xff0c;要求超时报错的同时&#xff0c;取消执行的 promise&#xff08;即不继续执行&#xff09;强缓存和协商缓存的区别token可以放在cookie里吗&#xff1f; 变量提升和函数提升的顺序 在JavaScript中&#…

Cesium之水流模型

关于Primitive。 Primitive和Entity&#xff0c;一般翻译成图元和实体&#xff0c;图元更接近底层&#xff0c;实体是封装后的高级对象&#xff0c;使用更加简便。一般来说&#xff0c;Primitive的使用相对繁琐&#xff0c;相比Entity需要使用者自己初始化更多对象&#xff0c…

LabVIEW开发血液动力学监测仪

LabVIEW开发血液动力学监测仪 心电图和光电容积描记图的缩写分别是心电图、心电图和PPG。都熟悉“心脏病发作”、“心力衰竭”、“冠状动脉疾病”和“中风”等术语&#xff0c;但中很少有人意识到这些疾病都被认为是心血管疾病。心脏病学是一个医学领域&#xff0c;专注于心脏…

树莓牌4B安装Centos8

准备工作 镜像&#xff1a;https://people.centos.org/pgreco/CentOS-Userland-8-stream-aarch64-RaspberryPI-Minimal-4/ 烧制工具&#xff1a;https://www.raspberrypi.com/software/ 初始化 将上述工具烧制好的SD卡插入树莓派&#xff0c;通电。通过网线将树莓派与电脑连…

回归预测 | MATLAB实现PSO-RBF粒子群优化算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现PSO-RBF粒子群优化算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现PSO-RBF粒子群优化算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&a…

C++初阶——string(字符数组),跟C语言中的繁琐设计say goodbye

前言&#xff1a;在日常的程序设计中&#xff0c;我们会经常使用到字符串。比如一个人的身份证号&#xff0c;家庭住址等&#xff0c;只能用字符串表示。在C语言中&#xff0c;我们经常使用字符数组来存储字符串&#xff0c;但是某些场景(比如插入&#xff0c;删除)下操作起来很…

在线HmacSHA256加密工具--在线获取哈希值又称摘要

具体请前往&#xff1a; 在线计算HmacSha256工具

【Unittest】Unittest接口测试框架开发-以登录模块为例

文章目录 框架结构框架目录结构封装被测试系统接口定义接口测试用例集成测试报告测试数据参数化&#xff08;一&#xff09;分析与数据构造&#xff08;二&#xff09;基于JSON实现参数化&#xff08;三&#xff09;基于数据库实现参数化 框架结构 框架结构包括&#xff1a;被…

Stable Diffusion:使用自己的数据集微调训练LoRA模型

Stable Diffusion&#xff1a;使用自己的数据集微调训练LoRA模型 前言前提条件相关介绍微调训练LoRA模型下载kohya_ss项目安装kohya_ss项目运行kohya_ss项目准备数据集生成关键词模型参数设置预训练模型设置文件夹设置训练参数设置 开始训练LoRA模型TensorBoard查看训练情况 测…

[JAVAee]Tomcat - Servlet

目录 Tomcat Servlet的工作 创建Servlet ①项目 ②依赖 ③目录 ④代码 ⑤打包 ⑥部署 ⑦验证 Servlet的运行原理 Servlet API HttpServlet 方法 处理Get/POST请求 HttpServletRequest 方法 获取请求中的信息 获取GET请求中的参数 获取POST请求中的参数…

基于YOLOv8模型和PCB电子线路板缺陷目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型PCB电子线路板缺陷目标检测系统可用于日常生活中检测与定位PCB线路板瑕疵&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检…

Dbeaver安装与报错解决方案

官网 https://dbeaver.io/ Dbeaver &#xff1a;社区版&#xff0c;开源免费。 下载 下载地址&#xff08;官网下载不了&#xff0c;在GitHub下载&#xff09;&#xff1a; https://github.com/dbeaver/dbeaver/releases 安装过程中的问题 双击安装包&#xff0c;按照提示…

第7步---MySQL的视图操作和

第7步---MySQL的视图操作 虚拟表。保存的只是视图的定义。不存放真实的数据&#xff0c;数据还是在原先的表中。 好处是方便和简化代码以及安全。 1.视图创建 数据准备 -- 创建表的测试数据 create table dept(deptno int primary key,dname varchar(20),loc varchar(20) ); …

基于Java+SpringBoot+vue前后端分离在线BLOG网站系统设计实现

基于JavaSpringBootvue前后端分离在线BLOG网站系统设计实现&#xff08;程序源码毕业论文&#xff09; 大家好&#xff0c;今天给大家介绍基于JavaSpringBootvue前后端分离在线BLOG网站系统设计与实现&#xff0c;本论文只截取部分文章重点&#xff0c;文章末尾附有本毕业设计完…

CPU缓存一致性原理

CPU缓存一致性原理 在本站的文章CPU缓存那些事儿中&#xff0c; 介绍了cpu的多级缓存的架构和cpu缓存行cache line的结构。CPU对于缓存的操作包含读和写&#xff0c;读操作在cache line中有所涉及&#xff0c;在本文中&#xff0c;将重点讨论CPU对于缓存进行写时的行为。 单核…