Java CompletableFuture使用示例

  在我之前的文章IO密集型服务提升性能的三种方法中提到过,提升IO密集型应用性能有个方式就是异步编程,实现异步时一定会用到Future,使用多线程+Future我们可以让多个任务同时去执行,最后统一去获取执行结果,这样整体执行的时长就取决于最长的一个任务,比如如下代码:

public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(2);System.out.println(new Date());Future<String> future1 = executorService.submit(() -> {Thread.sleep(5000);return new Date() + ":" + "thread1 result";});Future<String> future2 = executorService.submit(() -> {Thread.sleep(3000);return new Date() + ":" + "thread2 result";});try {System.out.println(future1.get());System.out.println(future2.get());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}

  上面代码的运行时长是5s,如果两个任务串行执行的化,运行时间就会是8s,当多个任务多线程异步执行时,最终总的执行时长取决于最长任务的执行时长。Future在大部分异步变成情况下,已经能很好的满足我们异步变成的诉求了,但当我看到CompletableFuture这个东西的时候,才发现Future还是太简单,我还是太年轻。

  CompletableFuture除了和Future一样可以获取执行结果外,它还**简化了异常、提供了手动设置结果的接口、链式操作、结果组合、回调,**接下来我们通过一些代码示例,来看下CompletableFuture这些特性如何吊打Future。

异常处理

  在使用Future时,你只能在get()的时候抓取到异常,异常处理会和结果获取的逻辑混在一起,有些时候处理起来会比较麻烦,而CompletableFuture提供了exceptionallyhandle 两个api,可以很方便的添加异常处理逻辑,后续只需要直接使用get获取结果即可,代码示例如下:

    public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(2);CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 故意抛出一个异常if (System.currentTimeMillis() % 2 == 0) {throw new RuntimeException("Exception occurred!");}return "Result";}, executorService);future = future.exceptionally(ex -> {// 对异常进行处理,并提供一个异常时的结果return "Default Value";});try {System.out.println(future.get()); // 输出 "Default Value"} catch (Exception e) {e.printStackTrace();}}

  exceptionally api让任务在运行过程中,如果抓到了Exception,就可以调用你给定的逻辑来处理异常。 与exceptionally就是handle方法,达到的效果一致,但使用略有不同,可以看出handle这个api是在任务执行完成后,讲exception和执行结果共同处理一次。

        future = future.handle((result, ex) -> {if (ex != null) {return "Default Value";}return result;});try {System.out.println(future.get()); // 输出 "Default Value"} catch (Exception e) {e.printStackTrace();}

手动设置结果

  在Future中,我们无法手动地完成一个任务或者设置任务的结果,这个任务的执行结果完全取决于任务的执行情况。但在CompletableFuture,我们可以在任何时间点上手动设置结果或者标记任务为已完成。举个例子,我要通过接口获取某个数据,但很要求时效性,如果超过1s我就不要接口调用结果了,而是返回一个默认值。Future设置等待时间也可以实现类似的功能,但不是很直观, CompletableFuture就很简单直观了。

public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(2);CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {// 随机sleep 0-2sThread.sleep(ThreadLocalRandom.current().nextInt(2000));} catch (InterruptedException e) {throw new RuntimeException(e);}return "Result";}, executorService);CompletableFuture<String> finalFuture = future;// 1s后手动完成executorService.submit(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 手动地完成这个 CompletableFuture,并设置计算结果finalFuture.complete("Default Value");});try {System.out.println(future.get()); // 概率性输出 "Default Value"} catch (Exception e) {e.printStackTrace();}}

链式操作

  链式操作意味着你可以将一个CompletableFuture的结果直接传递给另一个异步操作,而无需等待第一个操作完成。这使得你可以更容易地编写复杂的并发逻辑,这种非常适合需要将多个接口结果串起来的流程,有点像langChain,我们直接看示例代码:

public static void main(String[] args) {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 第一个异步任务return "Hello";}).thenApplyAsync(result -> {// 第二个异步任务,依赖第一个任务的结果return result + " World";}).thenApplyAsync(result -> {// 第三个异步任务,依赖第二个任务的结果return result + "!";});try {System.out.println(future.get());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}

  这个示例比较简单,在实际使用中,我们可以将三个任务替换成任何需要相互依赖的任务,你只需要定义每个任务的输入和输出,然后将它们连接在一起,就可以创建一个复杂的并发流程。

结果组合

  CompletableFuture提供了几个方法来组合或合并多个CompletableFuture的结果。最常用的可能是thenCombineallOf方法。

以下是thenCombine方法的使用示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {return "Hello";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {return "World!";
});CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> {return s1 + ", " + s2;
});try {String result = combinedFuture.get();System.out.println(result); // 输出 "Hello, World!"
} catch (InterruptedException | ExecutionException e) {e.printStackTrace();
}

  在上述代码中,我们首先创建了两个独立的CompletableFuture,然后使用thenCombine方法将它们的结果组合在一起。

  以下是allOf方法的使用示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {return "Hello";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {return "World!";
});CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);// 当所有的 CompletableFuture 都完成时,获取结果
allFutures.thenRun(() -> {try {String result1 = future1.get();String result2 = future2.get();System.out.println(result1 + ", " + result2); // 输出 "Hello, World!"} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}
});

  可以看出thenCombine和allOf都实现了类似的功能,但两者的侧重点不同,thenCombine实现的是对执行结果的聚合,而allOf实现的是对执行完状态的聚合,还是需要你去显式调用get()方法去获取结果的,所以你还是得根据具体需求来选择使用thenCombine还是allOf

回调

  使用Future时,我们只能通过get()或者isDone()方法来获取到任务是否执行完成了,无法让任务执行完成后主动通知到我们,但CompletableFuture提供了一些回调方法,可以让任务执行完成后执行结果处理的任务或者执行一些通知的逻辑。 CompletableFuture提供了几个方法来设置回调,如thenApply, thenAccept, 和 thenRun

具体看代码示例:

public static void main(String[] args) {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 异步任务return "Hello, World!";});future.thenApply(result -> {// 当 future 完成时,这个回调会被调用。// result 参数是 future 的结果。// 这个回调的结果会被包装成一个新的 CompletableFuture。return result.length();}).thenAccept(length -> {// 当前面的 CompletableFuture 完成时,这个回调会被调用。// length 参数是前面 CompletableFuture 的结果。// 这个回调没有返回值。System.out.println(length); // 输出 "Hello, World!" 的长度}).thenRun(() -> {// 当前面的 CompletableFuture 完成时,这个回调会被调用。// 这个回调没有输入参数,也没有返回值。System.out.println("Done");});}

  thenApply、thenAccept和thenRun都是在任务执行完成后被调起,但他们定位有所不同,thenApply处理的结果可以被包装成一个新的CompletableFuture,只后可以继续链式调用。但thenAccept只能接受输入,无法提供输出,所以他的定位是任务的收尾,比如可以将结果输出。 而thenRun既没有输入也没有输出,所以它只是获取到了任务执行完的状态,任务的执行结果是获取不到的,所以它是最弱的。

总结

  总结下本文,Future 尽管有用,但在功能上还是相对简单。CompletableFuture不仅提供了获取执行结果的能力,它还增添了异常处理、手动完成、链式操作、结果组合和回调等强大功能。

-异常处理: 利用exceptionallyhandle方法,可以简化异常处理流程,使其更加直观和易于管理。
-手动设置结果: CompletableFuture允许我们在任意时间点手动设置结果,增加了灵活性。
-链式操作: 通过链式操作,我们可以将不同的异步操作以流水线的方式串联起来,编写复杂的并发逻辑变得更加简单。
-结果组合: thenCombineallOf方法能够将多个CompletableFuture的结果合并,为我们提供了更多处理异步操作结果的方式。
-回调: 提供了thenApply, thenAccept, 和 thenRun等方法,允许我们在任务完成后进行结果处理或其他逻辑操作。

  通过这些特性,CompletableFuture显著地提升了异步编程的能力和灵活性。无论是简化异常处理,还是实现复杂的异步逻辑,CompletableFuture都能够帮助我们写出更清晰、更高效的代码。我相信通过本文的示例代码,你可以更深入地理解CompletableFuture的强大之处,并在你的项目中充分利用它来提升性能。

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

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

相关文章

Docker下安装可视化工具Portainer

目录 Portainer简介 Portainer安装 Portainer简介 Portainer是一款开源的容器管理平台&#xff0c;支持多种容器技术&#xff0c;如Docker、Kubernetes和Swarm等。它提供了一个易于使用的Web UI界面&#xff0c;可用于管理和监控容器和集群。Portainer旨在使容器管理更加简单…

如何在Ubuntu上安装pip3

一、检查系统是否安装pip3 首先&#xff0c;我们需要检查系统中是否已经安装了pip3。为此&#xff0c;我们可以运行以下命令&#xff1a; pip3 --version如果系统已经安装了pip3&#xff0c;您将会看到输出结果类似于&#xff1a; pip 19.0.3 from /usr/local/lib/python3.6…

内部类Lambda

静态内部类 /*** 静态成员是在类加载成字节码时就已经存在的,静态只能访问静态*/ public class Demo {public static void main(String[] args) {Outer.Inner.show();} }class Outer {int num1 10;static int num2 20;static class Inner {static void show() {Outer outer …

Kubernetes实战(六)-多系统架构容器镜像构建实战

1 背景 最近在一个国产化项目中遇到了这样一个场景&#xff0c;在同一个 Kubernetes 集群中的节点是混合架构的&#xff0c;即其中某些节点的 CPU 架构是 x86 的&#xff0c;而另一些节点是 ARM 的。为了让镜像在这样的环境下运行&#xff0c;一种最简单的做法是根据节点类型为…

6-15 复制字符串

#include<stdio.h> #include<string.h> int main(){int i;char s1[80],s2[80];printf("输入的s2是&#xff1a;");scanf("%s",s2);for(i0;i<strlen(s2);i)s1[i]s2[i];printf("复制后的s1是&#xff1a;%s\n",s1); return 0;}

ES如何提高召回率之【词干提取】

想要提高召回率就需要尽可能匹配相关的文档&#xff0c;其中一个办法就是在索引阶段对词语分析&#xff08;分词器&#xff09;的时候提取词干&#xff0c;搜索的时候也取词干。 不取词干 es默认使用的是标准的分词器&#xff0c;是不会取词干的。 但是标准分词器是包含小写转…

HttpRunner自动化工具之实现参数化传递

参数化实现及重复执行 参数化测试&#xff1a;在接口测试中&#xff0c;为了实现不同组数据对同一个功能模块进行测试&#xff0c;需要准备多组测试数据对模块进行测试的过程。 在httprunner中可以通过如下方式实现参数化&#xff1a; 1、在YAML/JSON 中直接指定参数列表 2、…

【STM32】STM32学习笔记-STM32简介(02)

00. 目录 文章目录 00. 目录01. STM32简介1.1 STM32是什么1.2 STM32应用领域1.3 STM32命名规则1.4 STM32选型 02. ARM简介2.1 ARM是什么2.2 ARM系列 03. STM32开发板3.1 MCU简介3.2 STM32开发板3.3 STM32硬件资源 04. STM32系统架构05. STM32引脚定义06. STM32启动配置07. STM3…

深入探讨Java设计模式:构建灵活而可维护的代码

深入探讨Java设计模式&#xff1a;构建灵活而可维护的代码 设计模式是在软件设计中&#xff0c;经过反复验证&#xff0c;以解决特定问题的最佳实践的经验总结。在Java中&#xff0c;设计模式是一种强大的工具&#xff0c;用于构建可扩展、灵活且易于维护的代码。本文将深入讨…

构建第一个ArkTS应用(纯HarmonyOS应用)

1. 安装开发工具 在华为开发者官方上下载HarmonyOS应用专用的开发工具&#xff0c;链接地址&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 要想使用开发工具让项目跑起来&#xff0c;需要10G的磁盘空间。开发工具需要的磁盘空间为2.36G&#xff1b;SDK需…

WT2003H语音芯片系列:通过bin文件实现板载语音更新,支持宽范围音频码率

随着科技的飞速发展&#xff0c;语音芯片已经成为了许多电子产品不可或缺的一部分。在这个领域中&#xff0c;WT2003H语音芯片系列以其卓越的性能和灵活的功能而备受瞩目。这一系列芯片具备一种独特的功能&#xff0c;即可以通过bin文件在板更新语音&#xff0c;同时音频码率支…

深度学习记录--logistic回归函数的计算图

计算图用于logistic回归函数 先回顾一下单一样本的logistic回归损失函数的公式&#xff0c;公式如下&#xff1a; 将logistic函数用计算图表示出来(以两个基础量为例)&#xff0c;计算图如下&#xff1a; 前向传播已经完成&#xff0c;接下来完成后向传播 运用链式法则依次求…

The Big IAM Challenge 云安全 CTF 挑战赛

The Big IAM Challenge 云安全 CTF 挑战赛 今天&#xff0c;我们来做一下有关于云安全 的CTF 挑战赛 The Big IAM Challenge,旨在让白帽子识别和利用 IAM错误配置&#xff0c;并从现实场景中学习&#xff0c;从而更好的认识和了解IAM相关的风险。比赛包括6个场景&#xff0c;每…

LeetCode 232.用栈实现队列

题目 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元素 int peek() 返回…

智跃人力资源管理系统GenerateEntityFromTable.aspx接口存在SQL注入漏洞 附POC

@[toc] 智跃人力资源管理系统GenerateEntityFromTable.aspx接口存在SQL注入漏洞 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者…

别再争国漫一哥了,真正的王者回来了!

相信很多漫迷都知道&#xff0c;《斗罗大陆》动画在播出的五年期间一直稳稳占领国漫各榜单第一的位置。现在它已经完结了大半年&#xff0c;尽管《斗破苍穹》《完美世界》等动画都在争国漫一哥&#xff0c;但排名总是上下浮动&#xff0c;没有定论。 但就在今天&#xff0c;《斗…

用GPT4.0对csdn问答社区内容进行总结的对话记录(20231203号)

问题链接&#xff1a;微信小游戏开发2D碰撞检测问题 问题内容&#xff1a; 用户在开发微信小游戏的2D项目时遇到了碰撞检测无效的问题。他们尝试使用其他的碰撞类&#xff0c;但在2D中会报错。用户怀疑微信小游戏的框架可能不支持2D碰撞检测&#xff0c;或者需要自己计算和编…

flink获取kafka的key value timestamp header

参考&#xff1a; http://t.csdnimg.cn/FvqEn

JUC-AQS

如何利用CAS实现一个同步框架 state&#xff1a;共享标记位。利用CAS修改&#xff0c;达到同步管理等待队列&#xff1a;存储需要等待获取锁的线程 共享标记位state0 表示资源是空闲的&#xff1b;state1表示有1个线程获取到资源&#xff0c;如何独占模式&#xff0c;判断持有…

一款充电桩解决方案设计

一、基本的概述 项目由IP6536提供两路5V 1.5A 的USB充电口&#xff0c;IP6505提供一路最大24W的USB快充口支持QC3.0 / DCP / QC2.0 / MTK PE1.1 / PE2.0 / FCP / SCP / AFC / SFCP的快充协议&#xff0c;电池充电由type-C输入经过IP2326输出最高15W快充对电池进行充电&#xf…