使用CompletableFuture实现并发计算-结合实例

一、什么是CompletableFuture?

      CompletableFuture 是Java 8引入的一个强大的并发工具类,它是Future接口的扩展实现。它提供了更丰富的异步编程模型和功能,允许开发者以非阻塞的方式处理异步计算的结果,并且可以将多个异步任务链式组合起来形成复杂的流程。

二、主要特点

1. 异步执行:

     通过supplyAsync()、runAsync()等方法可以提交一个Runnable或Callable到线程池中异步执行。

2. 结果获取:

     与传统的Future类似,可以通过get()方法来阻塞地获取异步任务的结果,或者使用thenApply(), thenAccept(), thenRun()等方法在前一个异步操作完成后进行后续处理,这些方法都不会阻塞当前线程。

3. 异常处理:

     提供.exceptionally()方法来捕获并处理Future执行过程中可能抛出的异常。

4. 组合异步操作:

    支持多种组合方式,如:
    - .thenCompose()用于基于前一个Future的结果启动另一个异步任务。
    - .allOf()用于等待一组CompletableFuture都完成。
    - .anyOf()用于等待一组CompletableFuture中的任何一个完成。

5. 流式API:

    其设计鼓励函数式的编程风格,使得代码更加简洁易读。

6. 取消操作:

    支持通过cancel(boolean mayInterruptIfRunning)方法取消正在进行的异步任务。

三、实例

1、以同时获取两个ETA,并将结果相加为例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class ETAExample {// 假设这是异步获取ETA的方法public static int getETA() {try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}return new Random().nextInt(100); // 返回一个模拟的ETA值}public static void main(String[] args) throws ExecutionException, InterruptedException {CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> getETA());CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> getETA());CompletableFuture<Integer> combinedFuture = CompletableFuture.allOf(future1, future2).thenApply(v -> future1.join() + future2.join());int finalETA = combinedFuture.get(); // 获取最终的ETA之和System.out.println("Final ETA: " + finalETA);}
}

.supplyAsync(),用于创建并执行两个异步任务来获取ETA;

.allof(),当两个获取ETA的CompletableFuture完成后,返回一个新的、已完成状态的 CompletableFuture。

.thenApply(),等.allof()执行完后,执行.thenApply()方法。本文中是将两个ETA加和;

.join(),CompletableFuture完成时返回结果值。执行过程中如果发生异常,会抛出一个unchecked异常。

2、异常处理

2.1 .exceptionally(Function<Throwable, T>)

      .exceptionally()可以为Future在执行过程中可能抛出的任何异常提供一个恢复策略。参数是一个函数,它接受一个Throwable对象(即异常),并返回一个替代的结果值。当Future由于异常而未能正常完成时,该函数会被调用,并且返回值将作为Future的新结果。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {if (/* 某些条件导致异常 */) {throw new RuntimeException("获取ETA失败");}return getETA(); // 假设这个方法会返回一个整数类型的ETA
});future.exceptionally(throwable -> {System.out.println("发生异常: " + throwable.getMessage());return -1; // 返回一个默认的ETA值或错误码
});int result = future.get(); // 此时无论是否发生异常,都会得到一个非null的结果

2.2 .handle(BiFunction<? super T, Throwable, U>)
     这个方法更通用,它同时处理成功和失败的情况。

     参数是一个BiFunction,其第一个参数是正常的计算结果(如果有的话),第二个参数是可能出现的异常。不管是正常结束还是异常终止,handle方法都会被执行,它可以统一处理两种情况下的结果。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> getETA());// 使用 handle 方法统一处理成功和失败的结果
future.handle((result, throwable) -> {if (throwable != null) { // 处理异常情况System.out.println("发生异常: " + throwable.getMessage());return -1;} else { // 处理正常情况return result * 2; // 假设这里对正常结果进行某种操作后再返回}
});Integer finalResult = future.get(); // 获取最终处理后的结果

2.3 try ... catch 

    此方法比较常见,本文就不再赘述了,大家可移步:【Java基础】[异常处理]try,catch,finally_catch块完全有可能得不到执行-CSDN博客

【JAVA基础】[异常处理]项目中悄无声息的RuntimeException_runtimeexception生成随机序列化的id-CSDN博客

     以上异常处理的方法,都可以帮我们保证系统的健壮性。

3、线程池:ForkJoinPool.commonPool()

      前面我们说过,.supplyAsync(),用于创建并执行两个异步任务来获取ETA,那这两个任务由哪个线程池管理并执行呢?答: ForkJoinPool.commonPool();

      ForkJoinPool.commonPool()不同于ThreadPoolExecutor,ForkJoinPool.commonPool()利用工作窃取机制,如果一个线程在其本地队列上没有找到任务时,它会尝试从其他线程的队列中“窃取”任务来执行。后面我计划单独出一篇文章来介绍。

      ForkJoinPool.commonPool()是一个全局共享的线程池,它为整个JVM进程提供服务。当在同一个工程中,多个线程或任务使用CompletableFuture.supplyAsync(...)方法时,如果不指定自
 定义的ExecutorService,那么默认情况下这些异步计算任务会提交到这个公共的ForkJoinPool中执行。这意味着所有依赖于commonPool()的任务将会共享同一组工作线程来并发执行。如果这些任务是CPU密集型且数量很大,可能会导致线程池资源紧张,从而影响性能。
    对于I/O密集型任务或者短生命周期的任务,公共池通常可以很好地复用线程资源。然而,在特定场景下,比如需要对线程数进行精细化控制、确保优先级或者隔离不同类型的计算任务时,建议创建并指定自定义的ForkJoinPool或其它类型的ExecutorService来处理异步任务。

    从官方文档中,我们可以看到,给出了两个异步执行方法:

1)static <U> CompletableFuture<U>    supplyAsync(Supplier<U> supplier)

返回一个新的CompletableFuture,它是由ForkJoinPool.commonPool()中运行的任务异步完成的,其值通过调用给定的Supplier获得。
2)static <U> CompletableFuture<U>    supplyAsync(Supplier<U> supplier, Executor executor)

返回一个新的CompletableFuture,它由给定执行器中运行的任务异步完成,其值通过调用给定的Supplier获得。

使用2),我们可以传入自定义的Executor。

四、使用过程中的注意事项

1. 异常处理:
     由于CompletableFuture的异步操作可能抛出异常,因此需要正确处理get()方法调用或者通过.exceptionally()、.handle()等方法捕获和处理可能出现的异常。前面我们结合实例也介绍过了。

2. Future的完成状态不可变:
    一旦CompletableFuture完成了(成功或失败),其结果是不可变的。所以我们不能更改已完成Future的结果。

3. 线程池配置:
    默认情况下,supplyAsync()会使用ForkJoinPool.commonPool()执行任务,所以如果我们的任务数量非常大或者执行时间较长,可能会对公共线程池造成压力,不同的业务之前可能引起资源竞争。在这种情况下,应该自定义ExecutorService来管理并发任务。

4. 取消任务:
    如果需要支持取消异步操作,可以调用CompletableFuture.cancel(true),但要确保任务是可以被中断的,并且在内部逻辑中检查Thread.currentThread().isInterrupted()以响应中断请求。

5. 链式操作顺序:
    使用.thenApply(), .thenAccept(), .thenCompose()等方法构建异步计算流程时,注意这些操作的执行顺序与它们在链中的位置保持一致。

6. 资源管理:
    在异步操作中打开的任何资源(如数据库连接、文件句柄)应在操作完成后正确关闭或释放。

7. allOf() vs anyOf():
    确保使用正确的组合器。CompletableFuture.allOf()等待所有Future都完成,而anyOf()只要任何一个完成就返回。这些就是字面意思,我们就不再赘述了。

8. 避免阻塞主线程:
    尽量避免在主线程或其他关键路径上直接调用get()阻塞等待结果。如果必须同步等待,考虑使用join()方法,它不会抛出InterruptedException。

9. 超时处理:
    可以结合CompletableFuture.get(long, TimeUnit)设置超时时间,防止因某个异步任务长时间未完成而导致整个程序挂起。

五、总结

     以上我们简要的了解了CompletableFuture的主要功能和在并发计算中的应用,我们可以看到CompletableFuture极大地简化了Java中的异步编程模型,提高了代码可读性和程序的响应能力,是构建高效、灵活并发应用的重要组件之一。后面将在应用中不断使用和加深理解。

参考:

Java Platform SE 8 ,GPT

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

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

相关文章

GoZero微服务个人探究之路(九)api文件编写总结

参考来源go-zero官方文档https://go-zero.dev/docs/tutorials 前言 go-zero是目前star最多的go语言微服务框架&#xff0c;api 是 go-zero特殊的语言&#xff0c;类型文件&#xff0c;go-zero自带的goctl可以通过.api文件生成http服务代码 api文件内容编写 不可使用关键字 …

Datawhale 组队学习之大模型理论基础 Task7 分布式训练

第8章 分布式训练 8.1 为什么分布式训练越来越流行 近年来&#xff0c;模型规模越来越大&#xff0c;对硬件&#xff08;算力、内存&#xff09;的发展提出要求。因为内存墙的存在&#xff0c;单一设持续提高芯片的集成越来越困难&#xff0c;难以跟上模型扩大的需求。 为了…

MATLAB|融合需求侧虚拟储能系统的楼宇微网优化调度¥29

目录 主要内容 模型研究 一、虚拟储能特征 二、楼宇微网虚拟储能 结果一览 下载链接 主要内容 该模型以楼宇为研究对象&#xff0c;围绕夏季制冷负荷&#xff0c;利用楼宇的蓄热特性&#xff0c;实现融合需求侧虚拟储能系统的楼宇微网优化调度模型&#xff0c;…

带【科技感】的Echarts 图表

Echarts脚本在线地址 https://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js 引入Echarts 脚本后粘贴代码 vue2 代码&#xff1a; <template><div><div ref"col-2-row-2" class"col-2-row-2"></div></div> <…

研究性学习:科技发展调查

1. 课题名称 科技发展调查 2. 起止时间 起始时间:2024年1月25日 结束时间:2024年2月20日 3. 项目组成员 组长:张三组员:李四、王五校内指导教师:杨老师校外指导教师:科技专家刘教授4. 组员分工情况 搜集整理资料: 张三:负责搜集西安市高铁领域的科技发展情况。李四…

力扣题目训练(1)

2024年1月25日力扣题目训练 2024年1月25日力扣题目训练225. 用队列实现栈257. 二叉树的所有路径258. 各位相加81. 搜索旋转排序数组 II82. 删除排序链表中的重复元素 II30. 串联所有单词的子串 2024年1月25日力扣题目训练 2024年1月25日开始进行编程训练&#xff0c;今天主要是…

《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第4章 朴素贝叶斯法

文章目录 第4章 朴素贝叶斯法4.1 朴素贝叶斯法的学习与分类4.1.1 基本方法4.1.2 后验概率最大化的含义4.2 朴素贝叶斯法的参数估计4.2.1 极大似然估计4.2.2 学习与 算法4.2.3 贝叶斯估计代码实践GaussianNB 高斯朴素贝叶斯scikit-learn实例scikit-learn:伯努利模型和多项式模型…

网安渗透攻击作业(1)

实现负载均衡 第一步&#xff1a;安装依赖 sudo apt insta11 libgd-dev 第二步&#xff1a;下载nginx wget http://nginx.org/download/nginx-1.22.1.tar.gz 第三步&#xff1a;对nginx进行解压 tar -zvxf nginx-1.22.1.tar.g2 第四步&#xff1a;编译安装nginx cd ngi…

通达信动量振荡指标公式(AO),反映市场驱动力的变化

动量振荡指标AO(Awesome Oscillator)衡量的是最近5根K线的动量与过去34根K线的动量对比&#xff0c;反映市场驱动力的变化。 一、动量振荡指标公式&#xff08;副图&#xff09; 动量振荡指标AO计算公式&#xff1a; 1、计算中间价&#xff0c;将最高价和最低价的和除以2&…

浅析HTTP协议

首先&#xff0c;前端请求后端数据&#xff0c;后端响应数据给前端&#xff0c;这是我们大家都知道的&#xff0c;那其中所涉及到的数据传输协议又是什么呢&#xff1f;这个传输规范就是我们大名鼎鼎的HTTP协议&#xff01; 什么是HTTP协议&#xff1f; HTTP&#xff08;超文本…

SqlAlchemy使用教程(六) -- ORM 表间关系的定义与CRUD操作

SqlAlchemy使用教程(一) 原理与环境搭建SqlAlchemy使用教程(二) 入门示例及编程步骤SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解SqlAlchemy使用教程(四) MetaData 与 SQL Express Language 的使用SqlAlchemy使用教程(五) ORM API 编程入门 本章内容&#xff0c;稍微有…

Java链表(1)

&#x1f435;本篇文章将对单链表进行讲解&#xff0c;模拟实现单链表中常见的方法 一、什么是链表 链表是一种逻辑结构上连续而物理结构上不一定连续的线性表&#xff0c;链表由一个一个节点组成&#xff1a; 每一个节点中都由数据域&#xff08;val&#xff09;和指针域&…

实战EDA电子设计自动化经典入门模型VHDL代码编写(含代码解释)上篇--状态机,逻辑设计:Y=AB+C

前言 电子设计自动化&#xff08;EDA&#xff09;&#xff1a; 定义&#xff1a;EDA是用于设计和开发复杂的电子系统&#xff08;如集成电路&#xff09;和印刷电路板的软件工具集合。这些工具通常用于设计电路、进行仿真测试、分析电路行为以及协助制造过程。应用&#xff1a;…

机器人学论文——智能施药机器人调研报告

目录 摘 要 Abstract 第一章&#xff1a;引言 1.1研究背景 1.2 研究意义 1.3文章架构 第二章&#xff1a;智能施药机器人发展现状 2.1引言 2.2 大田智能施药机器人发展现状 2.3 果园智能施药机器人发展现状 2.4 设施农业智能施药机器人发展现状 第三章&#xff1a;智能施药机器…

【知识---Linux 有哪些版本】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言Ubuntu&#xff1a;Debian&#xff1a;Fedora&#xff1a;CentOS&#xff1a;openSUSE&#xff1a;Arch Linux&#xff1a;Gentoo&#xff1a;Slackware&#x…

docker指令存档

目录 Docker 1、概念 2、架构图 3、安装 4、Docker怎么工作的&#xff1f; 5、Docker常用命令 帮助命令 镜像命令 1、查看镜像 2、帮助命令 3、搜索镜像 4、拉取镜像 5、删除镜像 容器命令 1、启动 2、查看运行的容器 3、删除容器 4、启动&停止 其他命令…

LeetCode 刷题总结 【未完待续】

双指针 快慢指针 27. 移除元素 26. 删除有序数组中的重复项左右指针 排序 快速排序 位运算 2859. 计算 K 置位下标对应元素的和

大数据数据流分析和处理的工具pig,从入门到精通!

介绍&#xff1a;Pig是一种数据流语言和运行环境&#xff0c;用于处理和分析大数据。 Pig由两个主要部分构成&#xff1a; Pig Latin语言&#xff1a;这是一种用于描述数据流的高级语言&#xff0c;它允许用户以较为简洁的方式编写数据处理和转换任务。 Pig执行环境&#xff1a…

【Vue3】状态管理工具——pinia的使用

目录 搭建pinia环境 存储数据 组件中使用数据 修改数据 storeToRefs $subscribe pinia相当于vue2中的vuex&#xff0c;pinia也是vue.js状态管理库。 搭建pinia环境 下载 npm install pinia 创建 src/main.js import { createApp } from vue import App from ./App.…

C++ 模拟实现mapset

目录 一、改造红黑树 1、模板T改造节点 2、提取节点中的key 3、迭代器类 operator operator-- 4、改造insert 5、红黑树迭代器 6、 普通迭代器构造const迭代器 二、set 三、map 在stl中map和set的结构中&#xff0c;他们都使用一个红黑树进行封装。 由上图可知&a…