研发必会-异步编程利器之CompletableFuture(上)

news2025/6/20 23:16:54

微信公众号访问地址:

近期热推文章:

    1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;

    2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;

    3、基于Redis的Geo实现附近商铺搜索(含源码)

    4、基于Redis实现关注、取关、共同关注及消息推送(含源码)

    5、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

    6、基于Redis实现点赞及排行榜功能

        一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。

CompletableFuture实现了Future接口。

一、Future简介

       JDK5新增了Future接口,它提供了一种异步并行计算的功能。若主线程要执行一个很耗时的计算任务,我们就可以通过future,把这个任务放到异步线程中去执行。主线程继续处理其他任务,异步线程处理完成后,再通过Future获取计算结果。

方法1:

@Test  public void testFuture() throws ExecutionException, InterruptedException {      ExecutorService executorService = Executors.newFixedThreadPool(5);      Future<String> future = executorService.submit(() -> {          Thread.sleep(2000);          return "hello";      });      System.out.println(future.get());      System.out.println("end");  }

      但是,Future无法解决多个异步任务需要相互依赖的场景,简单点说就:主线程需要等待子线程任务执行完毕之后再进行执行,这个时候你可能想到了CountDownLatch,没错确实可以解决,代码如下:第一个通过用户id获取用户信息,第二个通过商品id获取商品信息。

方法2:

@Test   public void testCountDownLatch() throws InterruptedException, ExecutionException {       ExecutorService executorService = Executors.newFixedThreadPool(5);       CountDownLatch downLatch = new CountDownLatch(2);       long startTime = System.currentTimeMillis();       Future<String> userFuture = executorService.submit(() -> {           //模拟查询商品耗时500毫秒           Thread.sleep(500);           downLatch.countDown();           return "用户A";       });       Future<String> goodsFuture = executorService.submit(() -> {           //模拟查询商品耗时500毫秒           Thread.sleep(400);           downLatch.countDown();           return "商品A";       });       downLatch.await();       //模拟主程序耗时时间       Thread.sleep(600);       System.out.println("获取用户信息:" + userFuture.get());       System.out.println("获取商品信息:" + goodsFuture.get());       System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");   }

运行结果:

获取用户信息:用户A  获取商品信息:商品A  总共用时1110ms

      从运行结果可以看出结果都已经获取,而且如果我们不用异步操作,执行时间应该是:500+400+600 = 1500,用异步操作后实际只用1110。可以发现,future+线程池异步配合,提高了程序的执行效率。

     但是Future对于结果的获取,不是很友好,只能通过阻塞或者轮询的方式得到任务的结果。

      1、Future.get()阻塞调用,在线程获取结果之前get方法会一直阻塞

      2、Future提供了一个isDone方法,可以在程序中轮询这个方法查询执行结果。

      虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便。阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源

      因此,JDK8设计出CompletableFuture。CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。

二、CompletableFuture简介

2.1、使用场景

2.2、创建异步任务

2.2.1、常见的有4种创建方式:

「supplyAsync」:执行任务,支持返回值;

「runAsync」:执行任务,没有返回值

1、supplyAsync方法

2、runAsync方法

2.2.2、结果获取4种方式

//方式一  在Future中就已经提供了 public T get()  //方式二   如果在指定时间内未获取结果将抛出超时异常public T get(long timeout, TimeUnit unit)  //方式三 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,//如果未计算完成将返回设定的valueIfAbsent值  public T getNow(T valueIfAbsent)  //方式四  方法里不会抛出异常public T join()

2.2.3、案例如下:

 public static void main(String[] args) {        //自定义线程池        ExecutorService executor= Executors.newCachedThreadPool();        //1、supplyAsync的使用        CompletableFuture<String> supplyAsync=CompletableFuture.supplyAsync(()->{            log.info("使用supplyAsync方法实现异步");            return "java成长之路"; },executor);        //supplyAsync的future,有返回值 join方法测试        log.info("输出返回结果为:"+supplyAsync.join());        //getNow方法测试        log.info("getNow方法测试结果:"+supplyAsync.getNow("java成长之路02"));        //2、runAsync的使用        CompletableFuture<Void> runAsync=CompletableFuture.runAsync(()                -> log.info("使用runAsync方法实现异步"),executor);        //runAsync的future没有返回值,输出null        log.info("输出返回结果为:"+runAsync.join());        //最后关闭线程池        executor.shutdown();        //3、get方法测试        CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((() -> 1 / 0));        log.info("get方法测试返回结果为:"+cp3.get());    }

输出结果

2.3、异步回调方法

2.3.1、thenRun/thenRunAsync

      做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值。

代码案例:

/**     * 功能描述:异步回调方法 testThenRun     * @MethodName: testThenRun     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/10 17:51     */    public void  testThenRun() throws ExecutionException, InterruptedException {        long startTime = System.currentTimeMillis();        CompletableFuture<Void> runAsyncRes= CompletableFuture.runAsync(()->{            try {                //执行任务1                log.info("任务1开始睡眠600ms。。。。");                Thread.sleep(600);            } catch (InterruptedException e) {                e.printStackTrace();            }        });        //做完第一个任务后,再做第二个任务 运行第二个任务        //thenRun是没有开启异步的        CompletableFuture<Void> thenRunRes=runAsyncRes.thenRunAsync(()->{            try {                //执行任务2                log.info("任务2开始睡眠400ms。。。。");                Thread.sleep(400);            } catch (InterruptedException e) {                e.printStackTrace();            }        });        //使用get方式获取thenRunRes的返回结果        log.info("使用get方式获取thenRunRes的返回结果:"+thenRunRes.get());        //模拟主程序耗时时间        Thread.sleep(600);        log.info("总共用时" + (System.currentTimeMillis() - startTime) + "ms");    }

输出结果:

区别:是否共用线程池

执行第一个任务的时候,传入了一个自定义线程池:

     1、调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池

      2、调用thenRunAsync方法执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池。

2.3.2.thenAccept/thenAcceptAsync

        第一个任务执行完成后,执行第二个回调方法任务,会将第一个任务的执行结果,作为第二个任务的入参,传递到回调方法中,但是回调方法是没有返回值的(任务有返回结果,对应回调方法有入参无返回值)

代码案例:

/**     * 功能描述:测试thenAccept/thenAcceptAsync     * @MethodName: testThenRun     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/10 18:19     */    public void  testThenAccept() throws ExecutionException, InterruptedException {        //执行任务1并返回结果        CompletableFuture<String> supplyAsyncRes=CompletableFuture.supplyAsync(()->{            return "res01";        });        //执行任务1的回调方法 任务1的执行结果作为回调方法的入参        CompletableFuture<Void> thenAccptRes=supplyAsyncRes.thenAccept((res)->{            log.info("任务1返回结果为:"+res);        });        //回调方法没有返回值        log.info("回调任务返回结果:"+thenAccptRes.get());    }

2.3.3、thenApply/thenApplyAsync

第一个任务执行完成后,执行第二个回调方法任务,会将第一个任务的执行结果,作为第二个任务的入参,传递到回调方法中,并且回调方法是有返回值的。(任务有返回结果,对应回调方法有入参有返回值)。

代码案例:

/**     * 功能描述:测试thenApply/thenApplyAsync     * @MethodName: testThenAccept     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/11 9:26     */    public void  testThenApply() throws ExecutionException, InterruptedException {        //执行任务并将结果传参给回调方法且返回回调结果        CompletableFuture<String> supplyAsyncRes=CompletableFuture.supplyAsync(()->{            return "res";        }).thenApply((a)->{            if(Objects.equals(a,"res")){                return "相同";            }            return "不同";        });        log.info("回调任务返回结果:"+supplyAsyncRes.get());    }

结果:

下面是异常回调:

2.3.4、exceptionally

某个任务执行异常时,执行的回调方法;并且有抛出异常作为参数,传递到回调方法。(回调方法有返回值)

代码案例:

/**     * 功能描述:exceptionally     * @MethodName: testExceptionally     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/11 9:36     */    public void  testExceptionally() throws ExecutionException, InterruptedException {        CompletableFuture<Object> exceptionallyRes = CompletableFuture.supplyAsync(()->{            log.info("当前线程为:"+Thread.currentThread().getName());            throw new RuntimeException();        }).exceptionally((e)->{           log.info("返回异常e:"+e);           return "程序异常.......";       });        log.info("回调任务返回结果:"+exceptionallyRes.get());    }

结果:

2.3.5、whenComplete方法

某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFuture的result是上个任务的结果。(回调方法无返回值)

当CompletableFuture的任务不论是正常完成还是出现异常它都会调用 「whenComplete」这回调函数

  • 正常完成」:whenComplete返回结果和上级任务一致,异常为null;

  • 出现异常」:whenComplete返回结果为null,异常为上级任务的异常;

即调用get()时,正常完成时就获取到结果,出现异常时就会抛出异常,需要你处理该异常。

代码案例:

/**     * 功能描述:whenComplete方法     * @MethodName: testExceptionally     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/11 9:48     */    public void  testWhenComplete() throws ExecutionException, InterruptedException {        //执行任务        CompletableFuture<Double> futureRes=CompletableFuture.supplyAsync(()->{            double math=Math.random();            log.info("产生的随机数为:"+math);            if(math<0.1){                throw new RuntimeException("出错了");            }            log.info("系统正常结束....");            return 0.12;        });        //异常回调方法        CompletableFuture<Double> whenCompleteRes=futureRes.whenComplete((res, throwable)->{            log.info("whenComplete返回的结果res是: "+res);            log.info("whenComplete返回的异常为 "+throwable);        });        log.info("回调任务返回结果:"+whenCompleteRes.get().toString());    }

正常返回,没有异常时候:

出现异常,返回结果为空,并回调方法返回异常:

 exceptionally结合示例:

/**     * 功能描述:whenComplete方法     * @MethodName: testExceptionally     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/11 9:48     */    public void  testWhenComplete() throws ExecutionException, InterruptedException {        //1、执行任务        CompletableFuture<Double> futureRes=CompletableFuture.supplyAsync(()->{            double math=Math.random();            log.info("产生的随机数为:"+math);            if(math<0.6){                throw new RuntimeException("出错了");            }            log.info("系统正常结束....");            return 0.12;        }).whenComplete((res, throwable)->{  //2、任务执行异常回调方法            log.info("whenComplete返回的结果res是: "+res);            log.info("whenComplete返回的异常为 "+throwable);        }).exceptionally((throwable)->{  //3、异常捕获            log.info("系统出现异常,需要处理:"+throwable.getMessage());            return 0.0;  //返回默认值        });        log.info("回调任务返回结果:"+futureRes.get());    }

返回结果:当出现异常时,exceptionally中会捕获该异常,给出默认返回值0.0

2.3.6、handle方法

       某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFuture的result是回调方法执行的结果。(回调方法有返回值)

代码案例:

 /**     * 功能描述:handle方法     * @MethodName: testWhenComplete     * @MethodParam: []     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/10/11 16:55     */    public void  testHandle() throws ExecutionException, InterruptedException {        //任务1返回结果        CompletableFuture<String> futureRes=CompletableFuture.supplyAsync(()->{            log.info("当前线程为:"+Thread.currentThread().getName());            try {                //执行任务1                log.info("任务1开始睡眠600ms。。。。");                Thread.sleep(600);            } catch (InterruptedException e) {                e.printStackTrace();            }            return "res01";        });        //回调函数        CompletableFuture<String> handleRes=futureRes.handle((res,throwable)->{            log.info("任务1返回结果:"+res);            if(res.equals("res01")){                log.info("任务1返回结果为res01");                return "相同";            }            return "不同";        }).exceptionally(throwable -> {            log.info("系统出现异常,需要处理:"+throwable.getMessage());            return "异常";  //返回默认值        });        log.info("回调函数返回结果:"+handleRes.get());    }

返回结果:

       如果大家对相关文章感兴趣,可以关注微信公众号"程序猿小杨",会持续更新优秀文章!欢迎大家 分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!

参考网站:

https://blog.csdn.net/ThinkWon/article/details/123390393

https://mp.weixin.qq.com/s/shjANruBk6VL492JaWLTEg

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

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

相关文章

应用在SMPS中的GaN/氮化镓

开关模式电源&#xff08;Switch Mode Power Supply&#xff0c;简称SMPS&#xff09;&#xff0c;又称交换式电源、开关变换器&#xff0c;是一种高频化电能转换装置&#xff0c;是电源供应器的一种。其功能是将一个位准的电压&#xff0c;透过不同形式的架构转换为用户端所需…

文心一言Plugin实战来了,测试开发旅游攻略助手

刚刚过去的8月&#xff0c;百度WAVE SUMMIT 深度学习开发者大会上&#xff0c;重磅发布文心一言的五个原生插件&#xff1a;百度搜索、览卷文档&#xff08;基于文档的交互&#xff09;、E 言易图&#xff08;数据洞察图表生成&#xff09;、说图解画&#xff08;基于图片的交互…

基于Redis+Cookie实现Session共享

分布式项目中要实现单点登录&#xff08;SSO - Single Sign On&#xff09;&#xff1a;对于同一个客户端&#xff08;例如 Chrome 浏览器&#xff09;&#xff0c;只要登录了一个子站&#xff08;例如 a.com&#xff09;&#xff0c;则所有子站&#xff08;b.com、c.com&#…

uniapp 运行到 app 报错 Cannot read property ‘nodeName‘ of null

uniapp 运行到某一个页面&#xff0c;报错&#xff0c;h5没有问题 Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repovuejs/coreat <GuiPagecustomHeadertruecustomF…

vue3和vue2生命周期​

vue2生命周期​ 每一个vue实例从创建到销毁的过程&#xff0c;就是这个vue实例的生命周期。在这个过程中&#xff0c;他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程 beforeCreate&#xff1a;是第一个生命周期函数&#xff0c;表示…

【微服务部署】七、使用Docker安装Nginx并配置免费的SSL证书步骤详解

SSL&#xff08;Secure Socket Layer&#xff0c;安全套接字层&#xff09;证书是一种数字证书&#xff0c;用于加密网站与访问者之间的数据传输。SSL证书是网站安全和可靠性的重要保证&#xff0c;是建立信任和保护用户隐私的重要手段。其作用可以总结为以下几点&#xff1a; …

Hadoop3教程(一):Hadoop的定义、组成及全生态概览

文章目录 &#xff08;1&#xff09;定义1.1 发展历史1.2 三大发行版本1.3 Hadoop的优势1.4 Hadoop的组成 &#xff08;13&#xff09;HDFS概述&#xff08;14&#xff09;Yarn架构&#xff08;15&#xff09;MapReduce概述&#xff08;16&#xff09; HDFS、YARN、MapReduce三…

git合并分支-IDEA

有1个主分支&#xff0c;我从主分支拉取过来了&#xff0c;数据然后改好了&#xff0c;现在想合并到主分支上&#xff0c;并且将主分支的内容更新到我的分支下。用git怎么操作? 1.将主分支(master)的内容合并到我的分支(master-shi)中 在我的分支下执行 git merge master ID…

自己一天在家没事就爱折腾

来吃馍馍了&#xff0c;看我自己动手做的花卷&#xff0c;味道一定美极了&#xff0c;哈哈&#xff0c;有没有想吃的呀&#xff01;快来我家&#xff0c;我和你一起分享。

Vue项目 -- 解决Eslint导致的console报错问题

在利用vue-cli3构建的项目中引入eslint进行语法检查时&#xff0c;使用console.log(‘xxx’)时&#xff0c;控制台抛出了Unexpected console statement (no-console) 异常&#xff0c; 例&#xff1a;一使用console就提示报错 解决办法是&#xff1a; 在 .eslintrc.js 文件中…

[Error]在Swift项目Build Settings的Preprocessor Macros中定义的宏无效的问题

问题 如图&#xff0c;在Build Settings -> Preprocessor Macros中添加了ISADEMO1。但在代码中判断无效&#xff0c;还是会输出“isn’t ADemo” #if ISADEMOprint("is ADemo") #elseprint("isnt ADemo") #endif解决 如图&#xff0c;要让Preproces…

RuntimeError: “LayerNormKernelImpl“ not implemented for ‘Half‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【AI视野·今日Robot 机器人论文速览 第五十二期】Wed, 11 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 11 Oct 2023 Totally 31 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers RoboHive: A Unified Framework for Robot Learning Authors Vikash Kumar, Rutav Shah, Gaoyue Zhou, Vincent Moens, Vittor…

【广州华锐互动】智轨列车AR互动教学系统

智轨列车&#xff0c;也被称为路面电车或拖电车&#xff0c;是一种公共交通工具&#xff0c;它在城市的街头巷尾提供了一种有效、环保的出行方式。智轨列车的概念已经存在了很长时间&#xff0c;但是随着科技的发展&#xff0c;我们现在可以更好地理解和欣赏它。通过使用增强现…

YOLOv3 | 核心主干网络,特征图解码,多类损失函数详解

https://zhuanlan.zhihu.com/p/76802514) 文章目录 1. 核心改进1.1主干网络1.2 特征图解码1.2.1 检测框&#xff08;位置&#xff0c;宽高&#xff09;解码1.2.2 检测置信度解码1.2.3 类别解码 1.3 训练损失函数1.3.1 正负样本定义1.3.2 损失函数 1. 核心改进 1.1主干网络 更…

React学习笔记 001

什么是React 1.发送请求获取数据 处理数据&#xff08;过滤、整理格式等&#xff09; 3.操作DOM呈现页面 react 主要是负责第三部 操作dom 处理页面 数据渲染为HTML视图的开源js库。 好处 避免dom繁琐 组件化 提升复用率 特点 声明式编程&#xff1a; 简单 组件化编程…

【Java学习之道】常用类库与工具

引言 Java类库是Java编程语言的一个重要组成部分&#xff0c;是Java程序员日常工作的必备工具。类库不仅提供了许多现成的、可直接使用的功能&#xff0c;还帮助我们简化了编程过程。 一、什么是Java类库 Java 类库是指由 Java 开发者社区共同维护和开发的一系列类和接口&am…

【从0开发】百度BML全功能AI开发平台【实操:以部署情感分析模型为例】

目录 一、全功能AI开发平台介绍二、AI项目落地应用流程&#xff08;以文本分类为例&#xff09;2-0、项目开始2-1、项目背景2-2、数据准备介绍2-3、项目数据2-4、建模调参介绍2-5、项目的建模调参2-6、开发部署2-7、项目在公有云的部署 附录&#xff1a;调用api代码总结 一、全…

Apache Doris (四十一): RESOTRE数据恢复

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. RESTORE数据恢复原理

④. GPT错误:导入import pandas as pd库,存储输入路径图片信息存储错误

꧂ 问题最初꧁ 用 import pandas as pd 可是你没有打印各种信息input输入图片路径 print图片尺寸 大小 长宽高 有颜色占比>0.001的按照大小排序将打印信息存储excel表格文件名 表格路径 图片大小 尺寸 颜色类型 占比信息input输入的是文件就处理文件 是文件夹&#x1f4c…