当前位置: 首页 > news >正文

并发编程系列-CompletableFuture

利用多线程来提升性能,实质上是将顺序执行的操作转化为并行执行。仔细观察后,你还会发现在顺序转并行的过程中,一定会牵扯到异步化。举个例子,现在下面这段示例代码是按顺序执行的,为了优化性能,我们需要将其改为并行执行。那具体的实施方法是什么呢?

//以下两个方法都是耗时操作
doBizA();
doBizB();

确实,实现并行化的方法很简单,就像下面的代码一样,我们创建两个子线程来执行这些操作。你会发现在下面的并行方案中,主线程无需等待doBizA()和doBizB()的执行结果,也就是说doBizA()和doBizB()这两个操作已经被异步化了。

new Thread(()->doBizA())
  .start();
new Thread(()->doBizB())
  .start();

异步化是实施并行方案的基础,更具体地说,它是实现利用多线程优化性能这一核心方案的基础。明白这一点后,你可能会理解为什么异步编程最近几年变得如此流行了,因为优化性能是互联网大厂的核心需求之一。Java在1.8版本引入了CompletableFuture来支持异步编程,这可能是你见过的最复杂的工具类了,但它的功能确实令人惊叹。

CompletableFuture的核心优势

为了体验CompletableFuture异步编程的优势,我们将使用CompletableFuture来实现一个烧水泡茶的程序。首先,我们需要制定分工方案。在下面的程序中,我们将任务分为三个部分:任务1负责洗水壶和烧开水,任务2负责洗茶壶、茶杯和取茶叶,任务3负责泡茶。任务3必须等待任务1和任务2都完成之后才能开始。下图展示了这个分工方案。

alt

烧水泡茶分工方案

下面是代码实现,你先略过runAsync()、supplyAsync()、thenCombine()这些不太熟悉的方法,从整体来看,你会发现:

  1. 无需手动维护线程,没有繁琐的手动线程管理工作,任务的线程分配也无需关注;
  2. 语义更明确,例如 f3 = f1.thenCombine(f2, ()->{}) 能够明确表达“任务3必须等待任务1和任务2都完成之后才能开始”;
  3. 代码更简洁且专注于业务逻辑,几乎所有的代码都是与业务逻辑相关的。
//任务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());

void sleep(int t, TimeUnit u) {
  try {
    u.sleep(t);
  }catch(InterruptedException e){}
}
// 一次执行结果:
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井

领略CompletableFuture异步编程的优势之后,下面我们详细介绍CompletableFuture的使用,首先是如何创建CompletableFuture对象。

创建CompletableFuture对象

创建CompletableFuture对象主要依靠下列四个静态方法,我们首先来看前两个。在烧水泡茶的例子中,我们已经使用了 runAsync(Runnable runnable)supplyAsync(Supplier<U> supplier),它们之间的区别是:Runnable 接口的run()方法没有返回值,而Supplier接口的get()方法有返回值。

前两个方法和后两个方法的区别在于:后两个方法可以指定线程池参数。

默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是CPU的核心数(也可以通过JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些耗时的I/O操作,就会导致线程池中的所有线程都被阻塞在I/O操作上,从而引发线程饥饿问题,进而影响整个系统的性能。因此,强烈建议你根据不同的业务类型创建不同的线程池,以避免彼此之间的干扰。

//使用默认线程池
static CompletableFuture<Void>
  runAsync(Runnable runnable)
static <U> CompletableFuture<U>
  supplyAsync(Supplier<U> supplier)
//可以指定线程池
static CompletableFuture<Void>
  runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U>
  supplyAsync(Supplier<U> supplier, Executor executor)

创建完CompletableFuture对象之后,会自动地以异步方式执行runnable.run()方法或者supplier.get()方法。对于一个异步操作,你需要关注两个问题:一个是异步操作何时完成,另一个是如何获取异步操作的执行结果。因为CompletableFuture类实现了Future接口,所以这两个问题都可以通过Future接口来解决。此外,CompletableFuture类还实现了CompletionStage接口,该接口包含了丰富的方法,仅在1.8版本中就有40个。对于这些方法,我们该如何理解呢?

如何理解CompletionStage接口

你可以从责任分工的角度来类比工作流程。任务之间存在时序关系,包括串行关系、并行关系和汇聚关系等。这样说可能有些抽象,为了更好地理解,我举一个前面烧水泡茶的例子。其中洗水壶和烧开水之间是串行关系,洗水壶、烧开水以及洗茶壶、洗茶杯这两组任务之间是并行关系,而烧开水、拿茶叶和泡茶则是汇聚关系。

alt

串行关系

alt

并行关系

alt

汇聚关系

CompletionStage接口可以清晰地描述任务之间的这种时序关系,例如前面提到的 f3 = f1.thenCombine(f2, ()->{}) 描述的就是一种汇聚关系。烧水泡茶程序中的汇聚关系是一种 AND 聚合关系,这里的AND指的是所有依赖的任务(烧开水和拿茶叶)都完成后才开始执行当前任务(泡茶)。既然有AND聚合关系,那就一定还有OR聚合关系,所谓OR指的是依赖的任务只要有一个完成就可以执行当前任务。

在编程领域,还有一个绕不过去的山头,那就是异常处理,CompletionStage接口也可以方便地描述异常处理。

下面我们就来一一介绍,CompletionStage接口如何描述串行关系、AND聚合关系、OR聚合关系以及异常处理。

1. 描述串行关系

CompletionStage接口里面描述串行关系,主要是thenApply、thenAccept、thenRun和thenCompose这四个系列的接口。

thenApply系列函数里参数fn的类型是接口Function<T, R>,这个接口里与CompletionStage相关的方法是 R apply(T t),这个方法既能接收参数也支持返回值,所以thenApply系列方法返回的是 CompletionStage<R>

而thenAccept系列方法里参数consumer的类型是接口 Consumer<T>,这个接口里与CompletionStage相关的方法是 void accept(T t),这个方法虽然支持参数,但却不支持回值,所以thenAccept系列方法返回的是 CompletionStage<Void>

thenRun系列方法里action的参数是Runnable,所以action既不能接收参数也不支持返回值,所以thenRun系列方法返回的也是 CompletionStage<Void>

这些方法里面Async代表的是异步执行fn、consumer或者action。其中,需要你注意的是thenCompose系列方法,这个系列的方法会新创建出一个子流程,最终结果和thenApply系列是相同的。

CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);

通过下面的示例代码,你可以看一下thenApply()方法是如何使用的。首先通过supplyAsync()启动一个异步流程,之后是两个串行操作,整体看起来还是挺简单的。不过,虽然这是一个异步流程,但任务①②③却是串行执行的,②依赖①的执行结果,③依赖②的执行结果。

CompletableFuture<String> f0 =
  CompletableFuture.supplyAsync(
    () -> "Hello World")      //①
  .thenApply(s -> s + " QQ")  //②
  .thenApply(String::toUpperCase);//③

System.out.println(f0.join());
//输出结果
HELLO WORLD QQ

2. 描述AND汇聚关系

CompletionStage接口里面描述AND汇聚关系,主要是thenCombine、thenAcceptBoth和runAfterBoth系列的接口,这些接口的区别也是源自fn、consumer、action这三个核心参数不同。它们的使用你可以参考上面烧水泡茶的实现程序,这里就不赘述了。

CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);

3. 描述OR汇聚关系

CompletionStage接口里面描述OR汇聚关系,主要是applyToEither、acceptEither和runAfterEither系列的接口,这些接口的区别也是源自fn、consumer、action这三个核心参数不同。

CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);

下面的示例代码展示了如何使用applyToEither()方法来描述一个OR汇聚关系。

CompletableFuture<String> f1 =
  CompletableFuture.supplyAsync(()->{
    int t = getRandom(5, 10);
    sleep(t, TimeUnit.SECONDS);
    return String.valueOf(t);
});

CompletableFuture<String> f2 =
  CompletableFuture.supplyAsync(()->{
    int t = getRandom(5, 10);
    sleep(t, TimeUnit.SECONDS);
    return String.valueOf(t);
});

CompletableFuture<String> f3 =
  f1.applyToEither(f2,s -> s);

System.out.println(f3.join());

4. 异常处理

虽然上面我们提到的fn、consumer、action它们的核心方法都 不允许抛出可检查异常,但是却无法限制它们抛出运行时异常,例如下面的代码,执行 7/0 就会出现除零错误这个运行时异常。非异步编程里面,我们可以使用try{}catch{}来捕获并处理异常,那在异步编程里面,异常该如何处理呢?

CompletableFuture<Integer>
  f0 = CompletableFuture.
    .supplyAsync(()->(7/0))
    .thenApply(r->r*10);
System.out.println(f0.join());

CompletionStage接口给我们提供的方案非常简单,比try{}catch{}还要简单,下面是相关的方法,使用这些方法进行异常处理和串行操作是一样的,都支持链式编程方式。

CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);

下面的示例代码展示了如何使用exceptionally()方法来处理异常,exceptionally()的使用非常类似于try{}catch{}中的catch{},但是由于支持链式编程方式,所以相对更简单。既然存在try{}catch{},那么必然还有try{}finally{},whenComplete()和handle()系列方法就类似于try{}finally{}中的finally{},无论是否发生异常都会执行whenComplete()中的回调函数consumer和handle()中的回调函数fn。whenComplete()和handle()的差异在于whenComplete()不支持返回结果,而handle()则支持返回结果的。

CompletableFuture<Integer>
  f0 = CompletableFuture
    .supplyAsync(()->(7/0))
    .thenApply(r->r*10)
    .exceptionally(e->0);
System.out.println(f0.join());

总结

曾经一提到异步编程,人们常会联想到回调函数,在JavaScript中,几乎所有的异步问题都依赖于回调函数来解决。然而,当处理异常和复杂的异步任务关系时,回调函数往往显得力不从心,这也导致了「回调地狱」(Callback Hell)的出现。在过去的几年里,异步编程备受诟病。

为了更好地支持异步编程,Java语言在1.8版本引入了CompletableFuture,并在Java 9版本中提供了更加完善的Flow API。

本文由 mdnice 多平台发布

相关文章:

并发编程系列-CompletableFuture

利用多线程来提升性能&#xff0c;实质上是将顺序执行的操作转化为并行执行。仔细观察后&#xff0c;你还会发现在顺序转并行的过程中&#xff0c;一定会牵扯到异步化。举个例子&#xff0c;现在下面这段示例代码是按顺序执行的&#xff0c;为了优化性能&#xff0c;我们需要将…...

锁粒度的粗细与时空损耗互换

1 空间换时间的cases 1.1 redis的用户分组限流和用户定制的限流器 Redis 用户分组限流和用户定制的限流器&#xff1a;使用 Redis 进行用户分组限流或用户定制的限流意味着你使用 Redis 数据库来维护用户的访问限制。可以通过计数器、滑动窗口或令牌桶等算法来实现限流。用户…...

[Android 11]使用Android Studio调试系统应用之Settings移植(七):演示用AS编译错误问题

文章目录 1. 篇头语2. 系列文章3. AS IDE的配置3.1 AS版本3.2 Gradle JDK 版本4. JDK的下载5. AS演示工程地址6.其他版本JDK导致的错误1. 篇头语 距离2021年开始,系列文章发表已经有近两年了,依旧有网友反馈一些gitee上演示源码编译的一些问题,这里就记录一下。 2. 系列文章…...

MyBatis面试题

MyBatis面试题&#xff1a; 1、MyBatis是什么&#xff1f; Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了JDBC&#xff0c;加载驱动、创建连接、创建statement等繁杂的过程&#xff0c;开发者开发时只需要关注如何编写SQL语句&#xf…...

Lorenz系统最大lyapunov exponent的求解

首先看下Lorenz混沌系统: 赋予初始值,例如: 当然,初始值可以根据需要设定。 看下他的吸引子,很美: 看下他的分叉图:...

c#实现策略模式

下面是一个使用C#实现策略模式的示例代码&#xff1a; using System;// 策略接口 public interface IStrategy {void Execute(); }// 具体策略类A public class ConcreteStrategyA : IStrategy {public void Execute(){Console.WriteLine("具体策略A的执行逻辑");} …...

家纺行业小程序商城搭建指南

家纺行业作为一个不可或缺的消费领域&#xff0c;近年来备受关注。随着互联网的发展&#xff0c;小程序商城成为家纺行业拓展市场的新利器。搭建一个家纺行业小程序商城并不是一件困难的事情&#xff0c;只需要按照以下几个步骤进行操作&#xff0c;就能轻松上手。 首先&#x…...

Python语法基础--条件选择

学习目标 使用比较运算符编写布尔表达式。使用random.randint(a,b)或者random.random()函数来生成随机数。编写布尔表达式(AdditionQuiz)。使用单向if语句实现选择控制。使用单向if语句编程。使用双向if-else语句实现选择控制。使用嵌套if和多向if-elif-else语句实现选择控制。…...

visual studio 2017 运行的程序关闭后不能再运行?(visual studio建立项目之后退出,如何再次完整打开项目?)

在你储存项目的文件夹里面应该是这样的 里面.vcxproj后缀名的就是原来创建的项目&#xff0c;直接打开这个头文件源文件就会一起出来了&#xff01; 真的管用&#xff0c;亲测有效。...

亚马逊feedback和review有什么区别

在亚马逊上&#xff0c;"Feedback"&#xff08;反馈&#xff09;和"Review"&#xff08;评论&#xff09;是两个不同的概念&#xff0c;它们在购物体验中起着不同的作用。 Feedback&#xff08;反馈&#xff09;&#xff1a; 亚马逊的"Feedback"…...

新疆大学841软件工程考研

1&#xff0e;软件生产的发展经历了三个阶段&#xff0c;分别是____、程序系统时代和软件工程时代时代。 2&#xff0e;可行性研究从以下三个方面研究每种解决方法的可行性&#xff1a;经济可行性、社会可行性和_____。 3&#xff0e;HIPO图的H图用于描述软件的层次关系&…...

Vue: el-form 自定义校验规则

Vue 的 el-form 组件可以使用自定义校验规则进行表单验证。自定义校验规则可以通过传递一个函数来实现&#xff0c;该函数接受要校验的字段的值作为参数&#xff0c;并返回一个布尔值或一个 Promise 对象。 下面是一个示例&#xff0c;演示如何在 el-form 中使用自定义校验规则…...

8.14 ARM

1.练习一 .text 文本段 .global _start 声明一个_start函数入口 _start: _start标签&#xff0c;相当于C语言中函数mov r0,#0x2mov r1,#0x3cmp r0,r1beq stopsubhi r0,r0,r1subcc r1,r1,r0stop: stop标签&#xff0c;相当于C语言中函数b stop 跳转到stop标签下的第一条…...

Flink笔记

下面是你提供的文字整理后的结果&#xff1a; 1. Flink是一个针对流数据和批数据的分布式处理引擎&#xff0c;同时支持原生流处理的开源框架。 - 延迟低(毫秒级)&#xff0c;且能够保证消息传输不丢失不重复。 - 具有非常高的吞吐(每秒千万级)。 - 支持原生流处理。…...

深度学习在MRI运动校正中的应用综述

运动是MRI中的主要挑战之一。由于MR信号是在频率空间中获取的&#xff0c;因此除了其他MR成像伪影之外&#xff0c;成像对象的任何运动都会导致重建图像中产生伪影。深度学习被提出用于重建过程的几个阶段的运动校正。广泛的MR采集序列、感兴趣的解剖结构和病理学以及运动模式&…...

内存不足V4L2 申请DMC缓存报错问题

当内存不足时,V4L2可能存在申请DMA缓存报错,如下日志: 13:36:54:125 [15070.640862] rkcifhw fdfe0000.rkcif: swiotlb buffer is full (sz: 1843200 bytes) 13:36:54:125 [15070.640891] rkcifhw fdfe0000.rkcif: swiotlb: coherent allocation failed, size=1843200 13:3…...

论文笔记--Llama 2: Open Foundation and Fine-Tuned Chat Models

论文笔记--Llama 2: Open Foundation and Fine-Tuned Chat Models 1. 文章简介2. 文章概括3 文章重点技术3.1 预训练Pretraining3.1.1 预训练细节3.1.2 Llama2模型评估 3.2 微调Fine-tuning3.2.1 Supervised Fine-Tuning(FT)3.2.2 Reinforcement Learning with Human Feedback(…...

客达天下项目案例

本资料转载于传智播客https://www.itheima.com/ https://space.bilibili.com/3493265607232348 黑马程序员主办的全日制统招大学——大同互联网职业技术学院 预计2024年开始招生&#xff0c;敬请持续关注&#xff01; B站视频入口&#xff1a;002_接口项目介绍_哔哩哔哩_bili…...

系统设计类题目汇总二

12 如何在实际的生产者端减少数据库的IO次数&#xff1f; 我自己想到的&#xff1a; 1 对于局部性很强的数据&#xff0c;启用mysql缓存机制&#xff0c;这样就不用磁盘IO 2 对于行数很多的表&#xff0c;可以分库分表&#xff0c;单表的数据量下来了&#xff0c;则查找索引要…...

MySQL和Redis如何保证数据一致性

MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性&#xff0c;很多应用程序将MySQL和Redis一起使用&#xff0c;其中MySQL作为主要的持久存储&#xff0c;而Redis作为主要的缓存。在这种情况下&#xff0c;应用程序需要确保MySQL和Redis中的数据是…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...