CompletableFuture 和 ExecutorService 的区别详解

news2024/9/29 11:22:10
引言

在现代Java开发中,异步编程和并发处理变得愈加重要。为此,Java提供了多种并发处理工具,其中最常用的包括ExecutorServiceCompletableFuture。它们都能帮助我们实现并发和异步任务处理,但各自的设计理念、应用场景和功能特点有很大不同。

本文将通过详细的讲解、图解和代码示例,深入探讨CompletableFutureExecutorService的区别。我们将从它们的基本原理出发,结合具体的应用场景,逐步剖析它们在并发处理中的不同作用,帮助开发者更好地理解和应用这两种工具。


第一部分:ExecutorService的概述

1.1 什么是ExecutorService?

ExecutorService是Java并发框架的一部分,它是一个接口,用于管理并发任务的执行。ExecutorService通过线程池来执行异步任务,可以帮助开发者管理线程的生命周期,避免手动创建和销毁线程的开销。

1.2 ExecutorService的工作机制

ExecutorService的核心工作机制是通过线程池管理任务的执行。线程池允许我们提交任务并在不同的线程中并发运行,而无需手动管理线程的创建、调度和回收。

  • 线程池:线程池是ExecutorService的核心,它维护了多个线程,供任务提交后使用。线程池可以通过不同的策略管理线程的重用、闲置和回收。
  • 任务提交:开发者可以通过submit()execute()方法向ExecutorService提交任务,线程池会负责任务的调度和执行。
  • 任务结果:对于需要返回结果的任务,ExecutorService会返回Future<T>对象,通过它可以获取任务的结果。

示意图:ExecutorService的工作原理

+----------------+       +--------------------+
| Task Submission| ----> |   Thread Pool       |
+----------------+       +--------------------+
                                |
                                v
                      +-------------------+
                      |   Task Execution   |
                      +-------------------+
1.3 ExecutorService的常见用法
import java.util.concurrent.*;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交一个任务
        Future<String> future = executorService.submit(() -> {
            Thread.sleep(1000);
            return "Task Completed";
        });

        try {
            // 获取任务的结果
            System.out.println(future.get());  // 输出:Task Completed
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

解释

  • 我们创建了一个固定大小的线程池,通过submit()方法提交了一个异步任务。
  • Future对象用于获取任务执行的结果。
  • executorService.shutdown()用于关闭线程池,确保程序结束时所有线程都正常回收。
1.4 ExecutorService的优缺点

优点

  • 线程池可以复用线程,减少频繁创建和销毁线程的开销。
  • 提供了灵活的任务提交方式和线程管理机制,适合处理并发任务。

缺点

  • ExecutorService的任务返回结果是Future<T>,获取结果时需要阻塞等待或轮询,不够灵活。
  • ExecutorService对任务之间的组合与依赖处理不够方便,无法简洁地表达任务链或多个任务的并行执行与组合逻辑。

第二部分:CompletableFuture的概述

2.1 什么是CompletableFuture?

CompletableFuture是Java 8引入的一个类,用于支持异步编程。它的设计初衷是简化任务的异步执行与结果的处理,提供了一种更加简洁和优雅的方式来管理异步任务的组合、依赖与回调。与ExecutorService不同,CompletableFuture内置了任务链与回调机制,可以轻松处理任务的组合、结果获取及异常处理。

2.2 CompletableFuture的工作机制

CompletableFuture采用异步任务链的方式执行操作,允许开发者以非阻塞的方式编写异步代码。它的核心思想是基于回调机制,在异步任务完成后,执行回调逻辑,从而避免了线程的阻塞。

  • 异步任务链CompletableFuture允许多个异步任务按照依赖关系进行串联和组合。每个任务的结果可以通过回调函数处理,从而避免了传统的阻塞式等待。
  • 异常处理CompletableFuture提供了灵活的异常处理机制,可以在任务链中捕获并处理异常。
  • 组合操作CompletableFuture可以将多个异步任务组合在一起,如并行执行多个任务并在所有任务完成后进行处理,或者将多个任务的结果合并成一个结果。

示意图:CompletableFuture的工作原理

Task 1 -----> Task 2 -----> Task 3 (回调执行)
                |
                v
             Task 4
2.3 CompletableFuture的常见用法
import java.util.concurrent.*;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // 异步执行任务,并设置回调函数
        CompletableFuture.supplyAsync(() -> {
            System.out.println("Executing Task...");
            return "Task Completed";
        }).thenApply(result -> {
            System.out.println("Processing Result: " + result);
            return result.length();
        }).thenAccept(resultLength -> {
            System.out.println("Result Length: " + resultLength);
        });

        // 主线程等待一段时间,确保异步任务完成
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

解释

  • supplyAsync()方法用于异步执行任务,并在任务完成后通过thenApply()thenAccept()方法进行结果处理。
  • thenApply()是一个转换操作,它将前一个任务的结果作为输入,并返回一个新的结果。
  • thenAccept()用于消费最终的结果,不返回任何内容。
2.4 CompletableFuture的优缺点

优点

  • 内置异步任务链支持,简化了复杂的任务依赖与组合。
  • 提供了回调机制,支持非阻塞式的结果处理。
  • 可以更方便地处理异常,并提供灵活的异常恢复机制。

缺点

  • 与传统同步代码相比,CompletableFuture的代码结构可能更难阅读和调试,尤其是当任务链变得复杂时。
  • 对比ExecutorServiceCompletableFuture的底层线程管理可能不如自定义线程池灵活。

第三部分:CompletableFuture 与 ExecutorService 的核心区别

虽然CompletableFutureExecutorService都用于并发任务的执行,但它们在设计理念、功能特性和应用场景上有显著差异。

3.1 设计理念的区别
  • ExecutorService:主要关注任务的执行管理,通过线程池执行异步任务,并提供任务的提交、调度和管理机制。任务的结果通过Future<T>对象获取,往往需要阻塞等待任务完成。

  • CompletableFuture:更加关注任务的组合和依赖处理,使用任务链和回调机制实现异步编程。任务完成后,开发者可以通过回调非阻塞地处理任务结果,无需阻塞等待。

3.2 任务结果处理的区别
  • ExecutorService:使用Future对象来获取任务结果,获取结果时需要调用future.get(),如果任务尚未完成,这会导致调用线程阻塞。

  • CompletableFuture:提供了丰富的回调函数(如thenApply()thenAccept()等)来非阻塞地处理任务结果。任务完成后,回调函数会自动执行,无需阻塞线程等待。

示意图:结果处理方式的对比

ExecutorService                          CompletableFuture

Task 1 -> Future.get() (阻塞等待)         Task 1 -> thenApply() (回调处理)
        -> Task 2                                  -> Task 2 (非阻塞)

代码示例:ExecutorService vs CompletableFuture

// ExecutorService 使用阻塞方式获取结果
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> future = executorService.submit(() -> {
    Thread.sleep(1000);
    return 42;
});
System.out.println("Result: " + future.get());  // 阻塞等待

// CompletableFuture 使用非阻塞方式处理结果
CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    return

 42;
}).thenAccept(result -> {
    System.out.println("Result: " + result);  // 回调处理
});
3.3 任务组合的区别
  • ExecutorService:不直接支持任务之间的组合或依赖处理。如果多个任务有依赖关系,需要通过手动控制任务的调度和依赖,代码复杂度较高。

  • CompletableFuture:内置了对任务组合的支持,允许多个任务串联、并行或组合执行。可以使用thenCombine()thenCompose()等方法将多个任务结果进行组合。

代码示例:任务组合

// CompletableFuture 任务组合示例
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);

CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
combinedFuture.thenAccept(result -> System.out.println("Combined Result: " + result));  // 输出:30
3.4 异常处理的区别
  • ExecutorService:通过try-catch块捕获Future.get()方法抛出的异常,处理异常的灵活性有限。

  • CompletableFuture:提供了专门的异常处理方法,如exceptionally()handle(),可以在任务链中灵活处理异常,并在必要时恢复任务结果。

代码示例:CompletableFuture 异常处理

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) throw new RuntimeException("Error!");
    return 42;
}).exceptionally(ex -> {
    System.out.println("Exception: " + ex.getMessage());
    return -1;  // 异常情况下返回默认值
});
future.thenAccept(result -> System.out.println("Result: " + result));

第四部分:CompletableFuture 与 ExecutorService 的最佳实践

4.1 何时使用ExecutorService?
  • 任务调度管理:当应用需要手动控制线程池的大小、任务的调度和生命周期管理时,ExecutorService是更好的选择。

  • 固定线程池应用:对于并发度较高的应用(如Web服务器或消息处理系统),可以使用ExecutorService创建固定大小的线程池,以便控制并发任务的数量。

  • 不依赖任务链:如果任务之间没有复杂的依赖关系或组合需求,ExecutorService的简单任务提交机制更合适。

4.2 何时使用CompletableFuture?
  • 任务依赖与组合:如果任务之间存在依赖关系,或者需要将多个异步任务组合在一起执行,CompletableFuture是更好的选择。它提供了丰富的组合操作符,简化了复杂任务链的处理。

  • 非阻塞异步处理:当需要异步执行任务并且不希望主线程阻塞时,CompletableFuture的回调机制非常适用。

  • 异常处理:如果任务执行中可能会抛出异常,并且需要灵活的异常恢复逻辑,CompletableFuture的异常处理方法(如exceptionally())可以提供更优雅的解决方案。

4.3 CompletableFuture与ExecutorService结合使用

在实际应用中,开发者可以将CompletableFutureExecutorService结合使用。CompletableFuture支持自定义线程池,因此可以通过ExecutorService作为CompletableFuture的底层线程池,既能利用ExecutorService的线程管理优势,又能使用CompletableFuture的任务组合和回调机制。

// 将 ExecutorService 作为 CompletableFuture 的线程池
ExecutorService customExecutor = Executors.newFixedThreadPool(5);

CompletableFuture.supplyAsync(() -> {
    System.out.println("Task executed by custom thread pool");
    return "Done";
}, customExecutor).thenAccept(result -> System.out.println("Result: " + result));

// 关闭线程池
customExecutor.shutdown();

第五部分:总结

ExecutorServiceCompletableFuture是Java并发编程中两个非常重要的工具。尽管它们都可以用于异步任务的执行,但它们各自的应用场景和功能特点有所不同。

  • ExecutorService更适合任务的调度与线程池管理,特别是在需要手动控制线程数、任务生命周期的场景中。
  • CompletableFuture则擅长处理任务的组合、依赖与非阻塞式的结果处理,特别适合异步编程。

通过图文和代码示例的详细讲解,我们深入分析了两者的核心区别以及各自在不同场景下的最佳实践。开发者可以根据具体的应用需求,选择合适的工具,以提高并发处理的效率和代码的可读性。

在实际项目中,也可以将这两种工具结合使用,充分利用它们各自的优势,构建更高效、健壮的异步并发系统。

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

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

相关文章

用ChatGPT做数据分析与挖掘,爽啊

最近已有不少大厂都在秋招宣讲了&#xff0c;也有一些在 Offer 发放阶段。 节前&#xff0c;我们邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对新手如何入门算法岗、该如何准备面试攻略、面试常考点、大模型技术趋势、算法项目落地经验分享等热门话题进行了…

34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题

在前面几篇文章中&#xff0c;我们讲了一些跟重构相关的理论知识&#xff0c;比如&#xff1a;持续重构、单元测试、代码的可测试性、解耦、编码规范。用一句话总结一下&#xff0c;重构就是发现代码质量问题&#xff0c;并且对其进行优化的过程。 前面的内容相对还是偏理论。…

笔记本维修与拆解(一)

清灰&#xff1a; 拆螺丝: 拔掉电池供电&#xff1a; 多按几次开机键&#xff0c;放电&#xff1a; 涂抹硅胶的时候&#xff0c;千万不要涂很多&#xff0c;溢出CPU&#xff0c;如果硅胶溢到焊盘上去的话很容易热胀冷缩短路 【联想拯救者Y9000P和R9000P最简单清灰教程&#xf…

2024年7月大众点评全国美发前百名城市分析

在做一些城市分析、学术研究分析、商业选址、商业布局分析等数据分析挖掘时&#xff0c;大众点评的数据参考价值非常大&#xff0c;截至2024年7月&#xff0c;大众点评美食店铺剔除了暂停营业、停止营业后的最新数据情况分析如下。 分析研究的字段维度包括大众点评数字id、字母…

「Python入门」vscode的安装和python插件下载

粗浅之言&#xff0c;如有错误&#xff0c;欢迎指正 文章目录 前言Python安装VSCode介绍VSCode下载安装安装python插件 前言 Python目前的主流编辑器有多个&#xff0c;例如 Sublime Text、VSCode、Pycharm、IDLE(安装python时自带的) 等。个人认为 vscode 虽然在大型项目上有…

创新大赛:如何在国赛现场赛中脱颖而出?

创新大赛&#xff1a;如何在国赛现场赛中脱颖而出&#xff1f; 前言创意与可行性问题定义讲故事商业价值数据支撑简化表达总结结语 前言 在当今这个快速变化的时代&#xff0c;创新已成为推动社会进步的重要动力。无论是科技、教育、医疗还是日常生活的方方面面&#xff0c;创新…

护眼落地灯到底有没有用?五款好用护眼落地灯分享

护眼落地灯到底有没有用&#xff1f;护眼落地灯既适合日常照明使用&#xff0c;又适合学生以及办公人群使用的一种护眼神器&#xff0c;因此热度一直都很高。但是该行业内的产品也很复杂&#xff0c;其中还有一些劣质不专业的产品掺杂在其中&#xff0c;不但照明效果不佳&#…

SpringBoot集成Matlab软件实战

在项目中处理矩阵等复杂数据结构的时候&#xff0c;可以用Matlab程序来运行&#xff0c;其优点是很多的。 专用工具箱和强大的矩阵运算能力&#xff1a;MATLAB 拥有强大的数学工具箱和优化工具箱&#xff0c;适合处理大规模矩阵运算以及水文模型的率定。MATLAB 的 Optimization…

关于HTML 案例_个人简历展示02

展示效果 用table进行布局label 标签进行关联 例如&#xff1a;点姓名就可以到text中去填写内容 input的使用 text 文本框radio 单选框select与option 选择框checkbox 复选框 textareaul与li 无序列表文中图片是本地的 链接: 图片下载地址 代码 <!DOCTYPE html> <…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》例10-9

灰度共生矩阵的相关性 相关性&#xff08;Correlation&#xff09; 公式 Correlation ∑ i 1 N g ∑ j 1 N g ( i − μ x ) ( j − μ y ) P ( i , j ) σ x σ y \text{Correlation} \frac{\sum_{i1}^{N_g} \sum_{j1}^{N_g} (i - \mu_x)(j - \mu_y) P(i,j)}{\sigma_x \…

基于elasticsearch存储船舶历史轨迹: 使用scroll滚动技术实现大数据量搜索

文章目录 引言I 轨迹索引的设计轨迹文档定时创建索引手动添加索引并为索引添加别名POST请求批量插入文档数据II 查询文档数据基于scroll滚动技术实现大数据量搜索查询轨迹查询参数返回dtoIII 知识扩展术语介绍基于 search_after 实现深度分页引言 需求: 存储轨迹,提供站点查…

获取用户openId存入数据库⑤

这一篇数微信公众号开发的第五篇&#xff0c;如果你是小白请点击下方第一篇的链接&#xff1a; 微信公众号开发-接口配置信息&#xff08;第①篇&#xff09;-CSDN博客 先获取token&#xff0c;代码&#xff1a; <?php //获取token $appId wx08888888888888888888; //改…

【代码】Zotero|用文章标题更新 Zotero 的参考文献引用条目信息的 Quicker 动作

如题。 目前只支持期刊和会议文章&#xff0c;并且只支持谷歌学术或 DBLP 能搜到的文章&#xff0c;知网的不支持&#xff0c;如果有人有需要我可以去试着写&#xff0c;但我很懒我看大家也没这个需求。 很早就写完了&#xff0c;一直忘记推了。 刚写完的时候心情是很激动的&a…

minio 快速入门+单机部署+集群+调优

目录 原理 概念 名词解释 Set /Drive 的关系 MinIO部署 单机 单机单盘 单机多盘 集群 多机单盘 多机多盘 配置负载均衡 调优 原理 MinIO是一个S3兼容的高性能对象存储&#xff0c;其主要特点如下&#xff1a; 适合存储大容量非结构化的数据&#xff0c;如图片&…

华为OD机试 - 静态扫描(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

jenkins 构建报错ERROR: Error fetching remote repo ‘origin‘

问题描述 修改项目的仓库地址后&#xff0c;使用jenkins构建报错 Running as SYSTEM Building in workspace /var/jenkins_home/workspace/【测试】客户端/client-fonchain-main The recommended git tool is: NONE using credential 680a5841-cfa5-4d8a-bb38-977f796c26dd&g…

深圳数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂

深圳正以前所未有的速度和力度&#xff0c;推进着数字经济与实体经济深度融合的新篇章。数字孪生工业互联网可视化技术作为关键驱动力&#xff0c;赋能新型工业化智能制造工厂&#xff0c;引领着深圳乃至全国制造业的转型升级。从精密制造到重型装备&#xff0c;从汽车电池到医…

喜讯!宝兰德荣获第三届“鼎新杯”数字化转型应用大赛二等奖

9月24日-25日&#xff0c;“2024数字化转型发展大会”在北京成功举办。来自政产学研用的专家学者、知名企业代表同台论道&#xff0c;共话数字化转型的新趋势。大会期间&#xff0c;备受瞩目的第三届“鼎新杯”数字化应用转型大赛结果正式揭晓&#xff0c;「宝兰德中间件统一管…

[240929] 12 款最佳免费开源隐写工具 | Llama 3.2: 开源、可定制模型,革新边缘人工智能和视觉体验

目录 12 款最佳免费开源隐写工具Llama 3.2: 开源、可定制模型&#xff0c;革新边缘人工智能和视觉体验 12 款最佳免费开源隐写工具 什么是隐写术&#xff1f; 隐写术是一种将信息隐藏在其他信息中的艺术和科学&#xff0c;除了发送者和预期的接收者之外&#xff0c;没有人会怀…

Agilent安捷伦N1914A双通道数字功率计操作说明

Agilent安捷伦N1914A双通道数字功率计操作说明 Keysight / Agilent N1914A EPM 系列双通道平均功率计 N1914A EPM 系列功率计为工作台/机架和现场应用提供可重复且可靠的结果。N1914A 提供高达 400 个读数/秒的测量速度&#xff0c;可实现快速、准确的平均功率测量&#xff0…