ExecutorCompletionService详解

news2024/12/23 18:22:48

本文已收录至Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录

文章目录

    • 摘要
    • ExecutorCompletionService适用场景
    • ExecutorCompletionService使用
    • ExecutorCompletionService原理解析
    • 注意事项
    • 总结

摘要

ExecutorCompletionService 是Java并发编程中的一个有用的工具类,它实现了 CompletionService 接口。ExecutorCompletionService 将 Executor 和BlockingQueue 功能融合在一起,使用它可以提交我们的任务。这个任务委托给 Executor 执行,可以使用 ExecutorCompletionService 对象的 take() 和 poll() 方法获取结果。

本文将深入讲解 ExecutorCompletionService 的使用以及源码解析。

ExecutorCompletionService适用场景

ExecutorCompletionService在以下场景中特别有用:

  • 并行任务处理:当需要同时执行多个任务,并按照完成的顺序获取它们的结果时,可以使用ExecutorCompletionService来简化任务提交和结果获取的流程。
  • 高性能计算:在需要进行大规模计算或复杂计算的场景中,可以将任务拆分成多个子任务,并使用ExecutorCompletionService来管理和获取子任务的结果。

假设现在有一批需要进行计算的任务,为了提高整批任务的执行效率,我们可以使用线程池来异步计算这些任务。通过向线程池中不断提交任务并保留与每个任务关联的Future对象。最后,我们可以遍历这些Future对象,并通过调用 get() 方法获取每个任务的计算结果。

Future的不足

Future 没有办法回调,只能手动去调用,当通过 get() 方法获取线程的返回值时,会导致阻塞,也就是和当前这个 Future 关联的计算任务执行完成的时候才返回结果,新任务必须等待已完成任务的结果才能继续进行处理。

这样会浪费很多时间,因为我们不知道哪个线程先执行完了,只能挨个去获取结果,这样已经完成的线程会因为前面未完成的线程的耗时而无法提前进行汇总,最好是谁先执行完成,谁先返回。

而 ExecutorCompletionService 可以实现这样的效果,节省获取完成结果的时间,它的内部有一个先进先出的阻塞队列,用于保存已经执行完成的 Future,通过调用它的 take() 方法或 poll() 方法可以获取到一个已经执行完成的 Future,进而通过调用 Future 接口实现类的 get() 方法获取最终的结果。

CompletionService的目标是任务谁先完成谁先获取,即结果按照完成先后顺序排序

ExecutorCompletionService使用

ExecutorCompletionService 提供了一种方便的方式来处理一组异步任务,并按照完成的顺序获取它们的结果。它内部使用了Executor框架来执行任务,并且内部管理着一个已完成任务的阻塞队列,在结果获取上提供了更加灵活和高效的机制。

下面是一个简单的例子来演示ExecutorCompletionService的基本使用:

public class ExecutorCompletionServiceExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        CompletionService<String> completionService = new ExecutorCompletionService<>(executor);

        // 提交任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            completionService.submit(() -> {
                double sleepTime = Math.random() * 1000;
                Thread.sleep((long) sleepTime); // 模拟耗时操作
                return "Task " + taskId + " completed,cost time: " + sleepTime;
            });
        }

        // 获取结果
        for (int i = 0; i < 10; i++) {
            Future<String> future = completionService.take();
            String result = future.get();
            System.out.println(result);
        }

        executor.shutdown();
    }
}

输出:

Task 2 completed,cost time: 170.01927312611775
Task 3 completed,cost time: 460.9622858036789
Task 1 completed,cost time: 563.24738180643
Task 0 completed,cost time: 595.938819219159
Task 5 completed,cost time: 480.4473056068137
Task 4 completed,cost time: 748.2343208613524
Task 6 completed,cost time: 370.4679098376097
Task 7 completed,cost time: 270.45945981324905
Task 9 completed,cost time: 336.5536570760892
Task 8 completed,cost time: 577.5774464801026

在上述代码中,我们创建了一个固定大小的线程池,并使用 ExecutorCompletionService 来提交和获取任务的结果。通过调用completionService.submit()方法来提交任务,并随机指定睡眠时间,来模拟任务执行的耗时,然后通过completionService.take()方法来获取已完成的任务结果。

可以看到是按照任务的执行耗时顺序去获取结果的。

ExecutorCompletionService原理解析

ExecutorCompletionService 提供了两个构造函数,一个可以指定阻塞队列,另一个使用内部默认的阻塞队列,两个构造函数都需要传进线程池参数。

提供了三个获取方法,可以看到都是从队列中获取。

  • take()/poll() 方法的工作都委托给内部的已完成任务队列 completionQueue。
  • 如果队列中有已完成的任务, take() 方法就返回任务的结果,否则阻塞等待任务完成。
  • poll() 与 take() 方法不同,poll() 有两个版本:
    • 无参的 poll() 方法:如果完成队列中有数据就返回,否则返回null。
    • 有参数的 poll() 方法:如果完成队列中有数据就直接返回,否则等待指定的时间,到时间后如果还是没有数据就返回null。

两个提交任务方法,可以看到 submit() 方法最终会委托给内部的 executor 去执行任务,提交任务的时候会将任务封装成 QueueingFuture 对象。

ExecutorCompletionService内部维护了 QueueingFuture 类,QueueingFuture 继承了 FutureTask,并重写了 done() 方法,

可以看到 done() 方法在任务完成的时候会将结果存进 已完成任务队列 completionQueue 中。

Futuretask 的 done() 方法是用来标记一个任务已经完成的方法。当一个 Futuretask 中的任务完成后,就会调用 done() 方法通知。

默认是空方法,不会执行任何动作。

执行流程

当我们使用ExecutorCompletionService类时,它能够按照任务完成的顺序获取它们的结果,这是因为ExecutorCompletionService类内部结合了QueueingFuture类和done()方法的机制。以下是源码流程步骤解释:

  1. 提交任务:
    • 我们通过submit方法将任务提交给ExecutorCompletionService。在提交任务时,ExecutorCompletionService会使用自定义的QueueingFuture类来包装任务,并将其交给底层线程池执行。
  2. QueueingFuture类:
    • QueueingFuture类是ExecutorCompletionService的内部类,继承自FutureTask。它的构造方法接收一个Callable对象作为参数。
    • 在QueueingFuture类中,它重写了done()方法。done()方法会在任务执行完成后被调用。
  3. 任务执行完成时的处理:
    • 当任务执行完成后,在底层线程池的Worker线程中,会调用QueueingFuture的done()方法。
    • 在done()方法中,QueueingFuture会首先调用父类FutureTask的done()方法,以触发对计算结果的获取。然后,它会将任务的结果存储到一个内部的BlockingQueue队列中(即completionQueue)。
  4. 获取任务结果:
    • 当我们调用take方法获取任务结果时,它会从completionQueue队列中取出已完成的任务结果,并返回该结果。如果队列为空,则会阻塞等待,直到有任务完成并返回结果。
    • take方法内部会调用QueueingFuture的get()方法,从而触发对应任务的计算结果的获取。
  5. 保证按顺序获取结果:
    • 由于completionQueue是一个阻塞队列,并且在done()方法中将任务结果按照完成的顺序放入队列中,因此我们可以通过按顺序获取队列中的任务结果,来保证按照任务完成的顺序获取它们的结果。

通过以上源码流程步骤,ExecutorCompletionService类能够按照任务完成的顺序获取结果。它利用QueueingFuture类包装任务并存储结果到阻塞队列中,在任务执行完成后,按照完成的顺序将结果放入队列,从而实现了按顺序获取结果的功能。

注意事项

在使用ExecutorCompletionService时,需要注意以下事项:

  • 合理选择线程池大小:根据任务的数量和复杂性,合理选择线程池的大小,以充分利用系统资源并避免资源浪费。
  • 及时处理异常:在任务执行过程中,如果发生异常,需要及时处理和记录异常信息,以保证程序的稳定性和可靠性。
  • 使用Future对象进行任务取消和超时控制:通过使用Future对象的cancel方法,可以取消正在执行的任务。同时,可以通过调整 poll 方法的参数来设置超时时间,避免长时间等待任务结果而导致阻塞。

总结

ExecutorCompletionService是一个强大且灵活的工具类,能够简化异步任务的处理和结果获取过程。通过使用ExecutorCompletionService,我们可以更加高效地处理一组异步任务,并按照完成的顺序获取它们的结果。

本文介绍了ExecutorCompletionService的基本使用方法,并对其源码进行了解析。希望通过这篇博客能够帮助读者更好地理解和应用ExecutorCompletionService。

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

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

相关文章

LeetCode做题总结 15. 三数之和(未完)

不会做&#xff0c;参考了代码随想录和力扣官方题解&#xff0c;对此题进行整理。 代码思路 思想&#xff1a;利用双指针法&#xff0c;对数组从小到大排序。先固定一个数&#xff0c;找到其他两个。 &#xff08;1&#xff09;首先对数组从小到大排序。 &#xff08;2&…

【网络技术】【Kali Linux】Wireshark嗅探(四)域名系统(DNS)

一、实验目的 本次实验使用wireshark流量分析工具进行网络嗅探&#xff0c;旨在了解域名系统&#xff08;DNS&#xff09;的工作原理。 二、域名系统概述 简单来说&#xff0c;域名系统&#xff08;Domain Name System, DNS&#xff09;将域名&#xff08;可以理解为“网址”…

为什么要用扫码出入库?

一、什么是扫码出入库管理系统 传统的仓库管理模式存在很多问题&#xff0c;如&#xff1a;货物积压、过期、丢失等。这些问题不仅影响了企业的正常运营&#xff0c;还给企业带来了经济损失。为了解决这些问题&#xff0c;扫码出入库管理系统应运而生。该系统采用先进的二维码…

Apache的配置与应用

目录 1、Apache简介 2、Apache连接保持 3、Apache的访问控制 3.1、客户机地址限制 3.2、用户授权限制 &#xff08;1&#xff09;创建用户认证数据文件 &#xff08;2&#xff09;添加用户授权配置 &#xff08;3&#xff09;验证用户访问授权 4、Apache日志分割 4…

Vivado JESD204B与AD9162建立通信实战总结

一、FPGA与AD9162的JESD204B接口 FPGA作为JESD204B接口的发送端&#xff0c;AD9162作为JESD204B接口的接收端。FPGA和AD9162的device clk、SYSREF由同源时钟芯片产生。其中&#xff0c;FPGA和AD9162的divice clk时钟不同&#xff0c;并且FPGA的decive clk等同于JESD204B IP的co…

【UnityShader入门精要学习笔记】(3)章节答疑

本系列为作者学习UnityShader入门精要而作的笔记&#xff0c;内容将包括&#xff1a; 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更&#xff0c;有始无终 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 复习&#xff08;阶段性总结…

数据分析案例-外国电影票房数据可视化分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

k8s 1.23.5版本安装ingress1.6.4

1、背景 网上找了好几个ingress 文件&#xff0c;可能是版本没对&#xff0c;ingress都没有安装成功&#xff0c;最后查了相关资料&#xff0c;手动安装了。 下面是版本的匹配列表 github中ingress 地址&#xff1a;https://github.com/kubernetes/ingress-nginx 1.23.5版本支…

MySQL的安装网络配置

目录 一. MySQL5.7的安装 二. MySQL8.0的安装 三. 配置网络访问 思维导图 一. MySQL5.7的安装 1. 解压 2. 将my.ini文件放入到解压文件中 3. 编辑my.ini文件&#xff0c;将路径改为当前路径 4. 进到bin目录下&#xff0c;以管理员身份打开cmd命令窗口 5. 安装MySQL服务 my…

HarmonyOS调研分享

经过十多年的发展&#xff0c;传统移动互联网的增长红利已渐见顶。万物互联时代正在开启&#xff0c;应用的设备底座将从几十亿手机扩展到数百亿 IoT 设备。GSMA 预测到 2025 年&#xff0c;全球物联网终端连接数量将达 246 亿个&#xff0c;其中消费物联网终端连接数量将达 11…

chatGPT带你学习设计模式 (二)抽象工厂模式(创建型模式) GURU

深入理解抽象工厂模式 引言 在面向对象编程中&#xff0c;对象的创建是一个常见且关键的挑战。尤其在需要管理一系列相关对象的创建时&#xff0c;传统的对象创建方法&#xff08;如直接使用 new 关键字&#xff09;可能导致代码的高耦合和低灵活性。这时&#xff0c;抽象工厂…

5+共病+WGCNA+机器学习+免疫浸润,经典共病生信思路,轻松拿5+

今天给同学们分享一篇生信文章“Identification of biomarkers for the diagnosis of chronic kidney disease (CKD) with non-alcoholic fatty liver disease (NAFLD) by bioinformatics analysis and machine learning”&#xff0c;这篇文章发表在Front Endocrinol (Lausann…

Spring-Retry 重试框架使用

一、Spring-Retry Spring-Retry框架是Spring自带的功能&#xff0c;具备间隔重试、包含异常、排除异常、控制重试频率等特点&#xff0c;是项目开发中很实用的一种框架。 支持手动调用方式和注解方式。 使用需引入下面依赖&#xff1a; <dependency><groupId>o…

Vue:脚手架Vue-CLI的使用

一、环境准备 vue脚手架&#xff08;vue-CLI&#xff09;的使用是基于nodejs环境下的。 你可以简单理解为&#xff0c;Java项目需要再jvm虚拟机上才能编译运行 nodejs的作用就是将vue文件编译成html、css、js代码文件。 如何安装nodejs 参考&#xff1a;https://blog.csdn.net…

Uibot (RPA设计软件)培训前期准备指南————课前材料

紧接着小北的上一篇博客&#xff0c;友友们我们即将开展新课的学习~RPA 培训前期准备指南——安装Uibot(RPA设计软件&#xff09;-CSDN博客https://blog.csdn.net/Zhiyilang/article/details/135348488?spm1001.2014.3001.5502 课程安排如下&#xff1a; 序号 日期 内容 视…

五、Spring AOP面向切面编程(基于注解方式实现和细节)

本章概要 Spring AOP底层技术组成初步实现获取通知细节信息切点表达式语法重用&#xff08;提取&#xff09;切点表达式环绕通知切面优先级设置CGLib动态代理生效注解实现小结 5.5.1 Spring AOP 底层技术组成 动态代理&#xff08;InvocationHandler&#xff09;&#xff1a;…

springcloud alibaba整合sentinel并结合dashboard控制面板设置规则

目录 一、springcloud alibaba整合sentinel二、采用代码方式设置流控规则三、结合dashboard控制面板设置规则3.1、准备工作3.2、设置全局异常处理3.3、编写测试接口3.4、结合dashboard控制面板设置规则3.4.1、流控规则设置并测试——QPS3.4.2、流控规则设置并测试——线程数3.4…

反编译有哪些优势

在现在这个信息化的时代&#xff0c;软件开发中的编程是关键步骤&#xff0c;了解编程的反编译同样至关重要。对于大多数人来说&#xff0c;编程和反编译似乎是两个相对比较陌生的概念&#xff0c;但是都在软件开发周期中起着至关重要的作用。尤其是反编译&#xff0c;它在多个…

nginx访问路径匹配方法

目录 一&#xff1a;匹配方法 二&#xff1a;location使用: 三&#xff1a;rewrite使用 一&#xff1a;匹配方法 location和rewrite是两个用于处理请求的重要模块&#xff0c;它们都可以根据请求的路径进行匹配和处理。 二&#xff1a;location使用: 1&#xff1a;简单匹配…

基于Java Servelet的学籍管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…