【异步编程】CompletableFuture:异步任务的选择(执行最快的)执行

embedded/2025/2/2 19:48:50/

文章目录

    • 一. applyToEither : 拿到第一个任务结束的结果
    • 二. runAfterEither :第一个任务完成后执行副作用
    • 三. acceptEither:消费第一个任务的结果
    • 四. 三种接口总结

对于两个异步任务,我们有时希望在其中一个任务完成时立即执行某些操作,而不必等待其他任务的完成。为了实现这一需求,CompletionStage 提供了几个有用的方法,其中包括 applyToEither()runAfterEither()acceptEither()

CompletableFuture对异步任务的选择执行不是按照某种条件进行选择的,而是按照执行速度进行选择的:前面两个并行任务,谁的结果返回速度快,谁的结果将作为第三步任务的输入。

一. applyToEither : 拿到第一个任务结束的结果

applyToEither() 方法允许我们在两个异步任务中选择一个已经完成的任务,并对其结果应用一个函数。无论哪个任务先完成,都会执行传入的 Function 操作,并返回一个新的 CompletableFuture

方法签名:

java">public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, ? extends U> fn)- **`other`**:另一个 `CompletionStage`,与当前任务并行执行。
- **`fn`**(两个任务中)当第一个任务完成时应用的函数。

使用场景:

applyToEither() 方法非常适合于需要在多个异步任务中选择一个最快完成的任务结果并进行处理的场景。

示例代码:

java">@Test  
public void applyToEitherDemo2() throws Exception {  CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(500);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 2;  });  CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(100);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 3;  });  CompletableFuture<Integer> result = future1.applyToEither(future2, (result1) -> {  System.out.println("第一个完成的任务的结果: " + result1);  return result1 * 2;  // 对结果进行处理  });  Integer integer = result.get();  System.out.println(integer);  }

分析

  • future1future2 两个异步任务并行执行。
  • applyToEither() 会选择第一个完成的任务,并将其结果传递给 fn
    这里是对结果进行乘以 2 的操作。
  • 如果 future2 完成更快(如上例中的500ms),则会输出 "第一个完成的任务的结果: 3" 并返回 6

 

二. runAfterEither :第一个任务完成后执行副作用

runAfterEither() 方法与 applyToEither() 类似,但它不关心任务的结果。它只在其中一个任务完成时执行一个没有返回值的操作。它适用于需要在多个任务中选择一个最先完成的任务并执行某个副作用操作(如打印日志、更新状态等)的场景。

方法签名:

java">public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action)- **`other`**:另一个 `CompletionStage`。
- **`action`**:当第一个任务完成时执行的 `Runnable` 操作。
- **返回类型**:返回一个新的 `CompletableFuture<Void>`,表示操作完成。

使用场景:

runAfterEither() 方法适用于当你只关心某个任务完成后执行某些副作用操作,而不需要处理任务的结果时。

示例代码:

java">@Test  
public void test() {  CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 2;  });  CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(500);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 3;  });  future1.runAfterEither(future2, () -> System.out.println("第一个完成的任务已经结束"));  
}

分析

  • future1future2 分别执行两个异步任务。
  • runAfterEither() 会在第一个任务完成后执行 Runnable 操作,这里是打印 "第一个完成的任务已经结束"
  • 无论哪个任务先完成,都会触发打印操作,而不关心它们的计算结果。

 

三. acceptEither:消费第一个任务的结果

acceptEither() 方法与 applyToEither() 方法类似,不同之处在于它接受一个 BiConsumer 来消费完成任务的结果,而不是返回一个新的 CompletableFuture。该方法适用于只需要消费结果的场景。

方法签名:

java">public void acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)- **`other`**:另一个 `CompletionStage`。
- **`action`**:当第一个任务完成时执行的 `Consumer` 操作。
- **返回类型**:该方法没有返回值,它会直接消费任务的结果。

使用场景:
acceptEither() 方法适用于当我们只关心任务的结果并需要消费(如打印、记录日志等)时,但不需要返回任何新的 CompletableFuture

示例代码:

java">@Test  
public void test2() {  CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 2;  });  CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(500);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  return 3;  });  //使用 join() 阻塞直到操作完成  future1.acceptEither(future2, (result) -> System.out.println("第一个完成的任务的结果: " + result)).join();  }**分析**- `future1` 和 `future2` 分别执行异步任务。
- `acceptEither()` 会选择第一个完成的任务,并将其结果传递给 `Consumer` 进行处理。在这里,我们将结果打印出来。
- 输出会是 `"第一个完成的任务的结果: 3"`,因为 `future2` 在500ms时先完成。

在测试代码中,通常会使用 join() 来确保异步任务完成后,主线程再退出,避免出现异步回调没有被执行的情况。

四. 三种接口总结

  • applyToEither():在两个异步任务中选择一个最先完成的任务,并对其结果应用一个函数,返回一个新的 CompletableFuture
  • runAfterEither():在两个任务中选择一个最先完成的任务,不关心任务的结果,只执行一个 Runnable 操作。
  • acceptEither():在两个任务中选择一个最先完成的任务,并对其结果执行一个 Consumer 操作,适用于消费任务的结果。

这三个方法提供了灵活的工具,可以帮助我们在多个并行任务中选择一个最先完成的任务,并根据需求对其结果进行处理或执行副作用操作。掌握这些方法,可以让我们更高效地进行异步编程和任务调度。


http://www.ppmy.cn/embedded/158995.html

相关文章

MySQL查询优化(三):深度解读 MySQL客户端和服务端协议

如果需要从 MySQL 服务端获得很高的性能&#xff0c;最佳的方式就是花时间研究 MySQL 优化和执行查询的机制。一旦理解了这些&#xff0c;大部分的查询优化是有据可循的&#xff0c;从而使得整个查询优化的过程更有逻辑性。下图展示了 MySQL 执行查询的过程&#xff1a; 客户端…

FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验

文章目录 FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验概述笔记my_fltk_test.cppfltk_test.hfltk_test.cxx用adjuster工程试了一下&#xff0c;好使。END FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验 概述 用fluid搭建UI…

Java-数据结构-优先级队列(堆)

一、优先级队列 ① 什么是优先级队列&#xff1f; 在此之前&#xff0c;我们已经学习过了"队列"的相关知识&#xff0c;我们知道"队列"是一种"先进先出"的数据结构&#xff0c;我们还学习过"栈"&#xff0c;是"后进先出"的…

【机器学习理论】生成模型和判别模型

生成模型和判别模型是机器学习中两种不同的建模方式。生成模型关注的是联合概率分布 P ( X , Y ) P(X, Y) P(X,Y)&#xff0c;即同时考虑数据 X X X和标签 Y Y Y的关系&#xff1b;判别模型则直接学习条件概率 P ( Y ∣ X ) P(Y|X) P(Y∣X)或决策边界。 生成模型 生成模型的目…

Lucene常用的字段类型lucene检索打分原理

在 Apache Lucene 中&#xff0c;Field 类是文档中存储数据的基础。不同类型的 Field 用于存储不同类型的数据&#xff08;如文本、数字、二进制数据等&#xff09;。以下是一些常用的 Field 类型及其底层存储结构&#xff1a; TextField&#xff1a; 用途&#xff1a;用于存储…

活动回顾和预告|微软开发者社区 Code Without Barriers 上海站首场活动成功举办!

Code Without Barriers 上海活动回顾 Code Without Barriers&#xff1a;AI & DATA 深入探索人工智能与数据如何变革行业 2025年1月16日&#xff0c;微软开发者社区 Code Without Barriers &#xff08;CWB&#xff09;携手 She Rewires 她原力在大中华区的首场活动“AI &…

ChatGPT 搜索测试整合记忆功能

据 TestingCatalog 报道&#xff0c;OpenAI 正在测试 ChatGPT 搜索的整合记忆功能&#xff0c;被命名为 “Memory in search”2。以下是关于该功能的具体情况123&#xff1a; 功能特点 个性化搜索&#xff1a;启用该功能后&#xff0c;ChatGPT 能利用存储的记忆数据&#xff0…

【自然语言处理(NLP)】深度学习架构:Transformer 原理及代码实现

文章目录 介绍Transformer核心组件架构图编码器&#xff08;Encoder&#xff09;解码器&#xff08;Decoder&#xff09; 优点应用代码实现导包基于位置的前馈网络残差连接后进行层规范化编码器 Block编码器解码器 Block解码器训练预测 个人主页&#xff1a;道友老李 欢迎加入社…