当Stream遇上CompletableFuture有什么问题?

01 前言

在系统开发过程中,常常会遇到接口响应时间过长的问题。

为了提升性能,多线程技术成为了首选的优化手段。

然而,在实际应用中,多线程的引入并不总能带来预期的性能提升。

本文将通过一个实际案例,深入探讨Stream与CompletableFuture结合使用时可能出现的问题,并分享解决问题的心得体会。

02 问题的产生

在优化一个耗时接口时,我采用了多线程技术。

以下是最初的代码实现:

List taskList;
Executor  executor;
taskList.stream()
    .filter(task -> task.isEnable())
    .map(task -> CompletableFuture.runAsync(() -> task.execute(), executor))
    .forEach(future -> future.join());

注解:

  1.  taskList.stream():将任务列表转换为流,以便进行后续的流操作。

  2. filter(task -> task.isEnable()):过滤掉未启用的任务。

  3. map(task -> CompletableFuture.runAsync(() -> task.execute(), executor)):为每个任务创建一个CompletableFuture,异步执行任务。

  4. forEach(future -> future.join()):等待所有异步任务完成。

然而,运行后发现接口响应时间并未显著改善,甚至略有增加。

这与预期的性能提升背道而驰。

03 问题定位与分析

为了找出问题的根源, 进行了以下实验:

控制变量法实验

首先,我尝试使用传统的线程池执行任务:

List taskList;
Executor executor;
taskList.stream()
    .filter(task -> task.isEnable())
    .forEach(task -> executor.execute(task::execute));

这段代码虽然提高了性能,但无法保证所有任务都执行完毕。

于是, 改用CompletableFuture并抛弃Stream:

List taskList;
Executor executor;
List> futureList = new ArrayList<>();
taskList.removeIf(task -> !task.isEnable());
for (MyTask task : taskList) {
    futureList.add(CompletableFuture.runAsync(() -> task.execute(), executor));
}
futureList.forEach(CompletableFuture::join);

这段代码既保证了任务的异步执行,又确保了主线程等待所有任务完成。

性能得到了显著提升。

基准测试与分析

为了进一步研究问题, 编写了多种代码实现并进行了基准测试。

测试结果显示,使用for循环和Stream结合CompletableFuture的耗时相近。

这表明,Stream的内部机制可能是问题的关键。

经过深入研究Stream的源码, 发现Stream的操作并非如我最初所认为的那样:

先对所有元素执行一个操作,再统一执行下一个操作。

实际上,Stream中的每个元素会依次执行所有的操作链。

这意味着,在最初的代码中,每个任务虽然被异步提交,但随后立即被join,导致实际上仍然是同步执行。

04 解决方案

为了解决这个问题, 调整了代码结构,将Stream操作和CompletableFuture的join操作分开:

List taskList;
Executor executor;
List> futureList = taskList.stream()
    .filter(task -> task.isEnable())
    .map(task -> CompletableFuture.runAsync(() -> task.execute(), executor))
    .collect(Collectors.toList());
futureList.stream()
    .forEach(CompletableFuture::join);

注解:

  1. taskList.stream():将任务列表转换为流。

  2. filter(task -> task.isEnable()):过滤掉未启用的任务。

  3. map(task -> CompletableFuture.runAsync(() -> task.execute(), executor)):为每个任务创建一个CompletableFuture。

  4. collect(Collectors.toList()):收集所有CompletableFuture到列表中。

  5. futureList.stream().forEach(CompletableFuture::join):对所有CompletableFuture进行join操作,确保所有任务完成。

这种写法充分利用了Stream和CompletableFuture的优势,实现了真正的异步执行。

你可能感兴趣的:(并发编程,java)