01、Java并发 Java ExecutorService

2023-11-19

ExecutorService 是 Java java.util.concurrent 包的重要组成部分,是 Java JDK 提供的框架,用于简化异步模式下任务的执行。

一般来说,ExecutorService 会自动提供一个线程池和相关 API,用于为其分配任务。

实例化 ExecutorService

实例化ExecutorService 的方式有两种:一种是工厂方法,另一种是直接创建。

Executors.newFixedThreadPool() 工厂方法创建 ExecutorService 实例

创建ExecutorService 实例的最简单方法是使用 Executors 类的提供的工厂方法。比如

ExecutorService executor = Executors.newFixedThreadPool(10);

当然还有其它很多工厂方法,每种工厂方法都可以创建满足特定用例的预定义 ExecutorService 实例。你所需要做的就是找到自己想要的合适的方法。这些方法都在 Oracle 的 JDK 官方文档中有列出

直接创建 ExecutorService 的实例

因为ExecutorService 是只是一个接口,因此可以使用其任何实现类的实例。Java java.util.concurrent 包已经预定义了几种实现可供我们选择,或者你也可以创建自己的实现。

例如,ThreadPoolExecutor 类实现了 ExecutorService 接口并提供了一些构造函数用于配置执行程序服务及其内部池。

ExecutorService executorService = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,   
  new LinkedBlockingQueue<Runnable>()
);

你可能会注意到,上面的代码与工厂方法 newSingleThreadExecutor() 的 源代码 非常相似。对于大多数情况,不需要详细的手动配置。

将任务分配给 ExecutorService

ExecutorService 可以执行 Runnable 和 Callable 任务。为了使本文简单易懂。我们将使用两个两个原始任务,如下面的代码所示。

Runnable runnableTask = () -> {
    try {
        TimeUnit.MILLISECONDS.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Callable<String> callableTask = () -> {
    TimeUnit.MILLISECONDS.sleep(300);
    return "Task's execution";
};

List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);

注意: 上面的代码使用了 lambda 表达式而不是匿名内部类。

创建完了任务之后,就可以使用多种方法将任务分配给 ExecutorService ,比如 execute() 方法,还有 submit()、invokeAny() 和 invokeAll() 等方法。这些方法都继承自 Executor 接口。

1、 首先来看看execute()方法;

该方法返回值为空 ( void )。因此使用该方法没有任何可能获得任务执行结果或检查任务的状态( 是正在运行 ( running ) 还是执行完毕 ( executed ) )。

executorService.execute(runnableTask);

2、 其次看看submit()方法;

submit() 方法会将一个 Callable 或 Runnable 任务提交给 ExecutorService 并返回 Future 类型的结果。

Future<String> future = executorService.submit(callableTask);

3、 然后是invokeAny()方法;

invokeAny() 方法将一组任务分配给 ExecutorService,使每个任务执行,并返回任意一个成功执行的任务的结果 ( 如果成功执行 )

String result = executorService.invokeAny(callableTasks);

4、 最后是invokeAll()方法;

invokeAll() 方法将一组任务分配给 ExecutorService ,使每个任务执行,并以 Future 类型的对象列表的形式返回所有任务执行的结果。

List<Future<String>> futures = executorService.invokeAll(callableTasks);

在继续深入理解 ExecutorService 之前,我们必须先讲解下另外两件事:关闭 ExecutorService 和处理 Future 返回类型。

关闭 ExecutorService

一般情况下,ExecutorService 并不会自动关闭,即使所有任务都执行完毕,或者没有要处理的任务,也不会自动销毁 ExecutorService 。它会一直出于等待状态,等待我们给它分配新的工作。

这种机制,在某些情况下是非常有用的,比如,,如果应用程序需要处理不定期出现的任务,或者在编译时不知道这些任务的数量。

但另一方面,这也带来了副作用:即使应用程序可能已经到达它的终点,但并不会被停止,因为等待的 ExecutorService 将导致 JVM 继续运行。这样,我们就需要主动关闭 ExecutorService。

要正确的关闭 ExecutorService,可以调用实例的 shutdown() 或 shutdownNow() 方法。

1、 shutdown()方法:;

executorService.shutdown();

shutdown() 方法并不会立即销毁 ExecutorService 实例,而是首先让 ExecutorService 停止接受新任务,并在所有正在运行的线程完成当前工作后关闭。
2、 shutdownNow()方法:;

List<Runnable> notExecutedTasks = executorService.shutDownNow();

shutdownNow() 方法会尝试立即销毁 ExecutorService 实例,所以并不能保证所有正在运行的线程将同时停止。该方法会返回等待处理的任务列表,由开发人员自行决定如何处理这些任务。

因为提供了两个方法,因此关闭 ExecutorService 实例的最佳实战 ( 也是 Oracle 所推荐的 )就是同时使用这两种方法并结合 awaitTermination() 方法。

使用这种方式,ExecutorService 首先停止执行新任务,等待指定的时间段完成所有任务。如果该时间到期,则立即停止执行。

executorService.shutdown();
try {
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
        executorService.shutdownNow();
    } 
} catch (InterruptedException e) {
    executorService.shutdownNow();
}

Future 接口

submit() 方法和 invokeAll() 方法返回一个 Future 接口的对象或 Future 类型的对象集合。这些 Future 接口的对象允许我们获取任务执行的结果或检查任务的状态 ( 是正在运行还是执行完毕 )。

Future 接口 get() 方法

Future 接口提供了一个特殊的阻塞方法 get(),它返回 Callable 任务执行的实际结果,但如果是 Runnable 任务,则只会返回 null。

因为get() 方法是阻塞的。如果调用 get() 方法时任务仍在运行,那么调用将会一直被执阻塞,直到任务正确执行完毕并且结果可用时才返回。

而且更重要的是,正在被执行的任务随时都可能抛出异常或中断执行。因此我们要将 get() 调用放在 try catch 语句块中,并捕捉 InterruptedException 或 ExecutionException 异常。

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
    result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

因为get() 方法是阻塞的,而且并不知道要阻塞多长时间。因此可能导致应用程序的性能降低。如果结果数据并不重要,那么我们可以使用超时机制来避免长时间阻塞。

String result = future.get(200, TimeUnit.MILLISECONDS);

这个get() 的重载,第一个参数为超时的时间,第二个参数为时间的单位。上面的实例所表示就的就是等待 200 毫秒。

注意,这个 get() 重载方法,如果在超时时间内正常结束,那么返回的是 Future 类型的结果,如果超时了还没结束,那么将抛出 TimeoutException 异常。

除了get() 方法之外,Future 还提供了其它很多方法,我们将几个重要的方法罗列在此

方法 说明
isDone() 检查已分配的任务是否已处理
cancel() 取消任务执行
isCancelled() 检查任务是否已取消

这些方法的使用方式如下

boolean isDone      = future.isDone();
boolean canceled    = future.cancel(true);
boolean isCancelled = future.isCancelled();

ScheduledExecutorService 接口

ScheduledExecutorService 接口用于在一些预定义的延迟之后运行任务和( 或 )定期运行任务。

同样的,实例化 ScheduledExecutorService 的最佳方式是使用 Executors 类的工厂方法。

Executors 类为很多类都提供了工厂方法,简直就是工厂方法的集大成者。

本章节为了简单起见,我们只使用只有一个线程的 ScheduledExecutorService 实例

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

有了实例之后,事情就好办了,比如,要在固定延迟后安排单个任务的执行,可以使用 ScheduledExecutorService 实例的 scheduled() 方法

Future<String> resultFuture = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

上面这个实例中的代码在执行 callableTask 之前延迟了一秒钟。

scheduled() 方法有两个重载,分别用于执行 Runnable 任务或 Callable 任务。

另外,ScheduledExecutorService 实例还提供了另一个重要方法 scheduleAtFixedRate() ,它允许在固定延迟后定期执行任务。

Future<String> resultFuture = service.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

上面的代码块将在 100 毫秒的初始延迟后执行任务,之后,它将每 450 毫秒执行相同的任务。

如果处理器需要更多时间来执行分配的任务,那么可以使用 scheduleAtFixedRate() 方法的 period 参数,ScheduledExecutorService 将等到当前任务完成后再开始下一个任务。

如果任务迭代之间必须具有固定长度的延迟,那么可以使用 scheduleWithFixedDelay() 方法 。例如,以下代码将保证当前执行结束与另一个执行结束之间的暂停时间为 150 毫秒。

service.scheduleWithFixedDelay(task, 100, 150, TimeUnit.MILLISECONDS);

根据scheduleAtFixedRate() 和 scheduleWithFixedDelay() 方法契约,在任务执行期间,如果 ExecutorService 终止了或任务抛出了异常,那么任务将自动结束。

ExecutorService 或 Fork/Join

Fork/Join 是 Java 7 提供的新框架,在 Java 7 发布之后,许多开发人员都作出了将 ExecutorService 框架替换为 fork/join 框架的决定。

但,这并不总是正确的决定。尽管 fork/join 使用起来更加简单且频繁使用时更带来更快的性能,但开发人员对并发执行的控制量也有所减少。

使用ExecutorService ,开发人员能够控制生成的线程数以及应由不同线程执行的任务粒度。ExecutorService 的最佳用例是处理独立任务,例如根据 「 一个任务的一个线程 」 方案的事务或请求。

而相比之下,根据 Oracle 文档,fork/join 旨在简化和加速工作,可以将任务递归地分成更小的部分。

后记

尽管ExecutorService 相对简单,但仍有一些常见的陷阱。我们罗列于此

1、 保持未使用的ExecutorService存活;

本文中对如何关闭 ExecutorService 已经做出了详细解释。
2、 使用固定长度的线程池时设置了错误的线程池容量;

使用ExecutorService 最重要的一件事,就是确定应用程序有效执行任务所需的线程数

  • 太大的线程池只会产生不必要的开销,只会创建大多数处于等待模式的线程。
  • 太少的线程池会让应用程序看起来没有响应,因为队列中的任务等待时间很长。
    3、 在取消任务后调用Future的get()方法;

尝试获取已取消任务的结果将触发 CancellationException 异常。
4、 使用Future的get()方法意外地阻塞了很长时间;

应该使用超时来避免意外的等待。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

01、Java并发 Java ExecutorService 的相关文章

  • Spring部署期间依赖注入问题

    我正在启动一个 Primefaces Spring Hibernate 项目 并且仍在学习如何正确处理这些组件 但就在此时 我面临着一个与 spring 依赖注入相关的问题 这让我很害怕 我已经在网上寻找答案两天了 但找不到我的代码有什么问
  • GSON 是一个 Java Throwable

    我有一个对象 其中包含一段数据和关联的异常 Public class MyBean Private String data Private Exception problem 当我尝试GSON toJSON 对象 它给了我一个循环引用抱怨
  • Java中的整数除法[重复]

    这个问题在这里已经有答案了 这感觉像是一个愚蠢的问题 但我在 Java 文档中找不到答案 如果我声明两个 int 然后将它们相除 到底发生了什么 他们是否转换为floats doubles首先 划分 然后投射回integer 或者除法是作为
  • Thread.yield()之后线程的Thread.State是什么?

    是什么Thread State之后的一个线程Thread yield 是不是一个Thread State WAITING 谢谢 不 线程仍会在RUNNABLE http download oracle com docs cd E17409
  • 在 IntelliJ 插件中创建后台任务

    我正在开发一个 IntelliJ idea 插件 并希望在后台任务中运行代码 在后台任务对话框和 UI 之外的另一个线程中可见 我发现了以下内容助手类 https github com inmite android selector cha
  • Java 中的递归回溯解决填字游戏

    我需要在给定初始网格和单词的情况下解决填字游戏 单词可以多次使用或根本不使用 初始网格如下所示 这是一个单词列表示例 pain nice pal id 任务是填充占位符 水平或垂直长度 gt 1 像那样 p pain pal id i c
  • 线程“main”中出现异常 java.lang.NoClassDefFoundError:无法初始化类 com.sun.jersey.core.header.MediaTypes

    我正在尝试运行一个球衣客户端并面临这个问题 WS级 import javax ws rs GET import javax ws rs Path import javax ws rs Produces import javax ws rs
  • 包java.time不存在,jdk1.8

    嗯 我刚刚开始从事代号工作 我对 Java 有相当不错的经验 我的代码一切都很好 没有任何问题 但在编译时我得到了这个 error package java time does not exit import java time Local
  • WAR 文件在 Tomcat 服务器中抛出 OutOfMemoryError

    我有一个 Spring MVC WAR 文件 可以在我的本地计算机 程序和网站 中完美运行 一旦我将文件上传到服务器 aTomcat 7 并尝试访问它 catalina 日志文件表明java lang OutOfMemoryError 我尝
  • 如何将 Google proto 时间戳转换为 Java LocalDate?

    我们需要将 Google Protobuf 时间戳转换为正常日期 在这种情况下 有没有办法将 Google Proto 缓冲区时间戳转换为 JavaLocalDate直接地 tl dr 作为 UTC 时刻 转换为java time Inst
  • 从相对路径读取文件

    我知道这个问题之前已经被问过 1000 次了 我确实尝试了所有解决方案 Java项目中如何从相对路径读取文件 java io File 找不到指定的路径 https stackoverflow com questions 3844307 h
  • Java:使用类型参数访问私有构造函数

    这是后续这个关于java私有构造函数的问题 https stackoverflow com questions 2599440 accessing the private constructor 假设我有以下课程 class Foo
  • TestNG 与 DataProvider 并行执行

    我有一个从数据提供者接收数据的测试 我希望此测试与数据提供者的不同值并行运行 我尝试了这样的方法 public class IndependentTest Test dataProvider dp1 threadPoolSize 3 inv
  • 从命令行将 clojure 源代码编译为类(AOT)(不使用 lein)

    我正在尝试将 clojure 源代码编译成类文件 并仅使用命令行运行它 没有 lein 也没有 可能 回复 我有 core cljsrc hello目录 src hello core clj 这是源代码 ns hello core defn
  • 无法向 openfire 服务器发送消息

    我无法使用 SMACK API 向 openfire 服务器上的 XMPP 客户端发送消息 我不确定我哪里出错了 我在 gtalk 上测试了相同的代码 它工作正常 public class SenderTest public static
  • 字符串包含相同的字符但仍然不同[重复]

    这个问题在这里已经有答案了 我正在尝试读取一个 txt 文件并使用每个句子作为团队的名称 同时使用该名称查找另一个 txt 文件以获取其内容 所有 txt 文件都位于我的资产文件夹的根目录中 第一个 txt 文件工作正常 我使用assetm
  • 如何在 Java 中以编程方式获取接口的所有实现的列表?

    我可以通过反思或类似的方式来做到这一点吗 我已经搜索了一段时间 似乎有不同的方法 这里总结一下 反思 https github com ronmamo reflections如果您不介意添加依赖项 该库非常受欢迎 它看起来像这样 Refle
  • 三角形未在 OSX 上的 OpenGL 2.1 中绘制

    我正在学习有关使用 OpenGL 在 Java 中创建游戏引擎的教程 我正在尝试在屏幕上渲染一个三角形 一切运行良好 我可以更改背景颜色 但三角形不会显示 我还尝试运行作为教程系列的一部分提供的代码 但它仍然不起作用 教程链接 http b
  • 使用 Java 8 时间将时间从一个时区转换为另一时区

    我正在尝试将日期转换为GMT 5 30 to EST与java 8ZonedDateTime String inputDate 2015 04 30 13 00 DateTimeFormatter sourceFormatter DateT
  • Oracle 的商业 Hotspot JVM 相对于 OpenJDK 有哪些性能优势?

    正如这个问题中所描述的 OpenJDK 与 Java HotspotVM https stackoverflow com q 44335605 1593077 Oracle 的商业 Hotspot JVM 本质上是 OpenJDK 加上一些

随机推荐

  • Python django jwt 报错 unexpected keyword argument ‘verify‘解决方法

    本文主要介绍Python中 使用django jwt时报错 TypeError decode got an unexpected keyword argument verify 的解决 原文地址 Python django jwt 报错 u
  • 【MongoDB】docker部署mongdb多机集群(跨主机副本集)

    目录 概述 MongoDB Replication 官方英文文档 冗余 数据可用性 MongoDB副本集 多主机多节点docker部署流程 概述 环境准备 部署步骤 参考文献 概述 MongoDB Replication 官方英文文档 单主
  • python转化为json格式

    转化为JSON格式 JSON是一种常用的数据交换格式 该格式在前端与后端数据传输和处理中非常常见 在Python中将数据转化为JSON格式是一个非常常见的需求 Python中自带的json模块可以非常方便地实现这个需求 转化为json格式
  • 【Linux】浅谈Linux内核定时器timer_list

    目录 1 平台说明 2 定时器timer list说明 2 1 所在头文件 2 2 结构体 2 3 重要函数说明 2 4 函数API 2 5 使用方法 3 实例 3 1 驱动程序代码 3 2 测试程序代码 3 3 结果 1 平台说明 Lin
  • 【源码篇】基于SpringBoot+thymeleaf图书馆管理系统

    1 系统介绍 系统总体功能介绍 1 分为系统管理员和用户两类用户 2 开放用户注册功能 3 系统管理员拥有用户管理 图书管理 以及用户的借书申请的确定和还书操作 4 用户只能查询图书 并进行借书操作 提出借书申请 每个用户最多借阅8本 即当
  • 2018年AI成败将见分晓;全球经济重心东移,上海料将在2035年超越巴黎

    2017临近结束 我们先来看看对于来年 都有些什么预测 埃森哲预测CES 2018 AI 5G 区块链以及无人车 明年1月9 12日 CES又将在拉斯维加斯举行 每年 咨询巨头埃森哲 Accenture 都会对CES上的技术趋势做出预测 这
  • 100天精通Python(可视化篇)——第91天:Pyecharts绘制各种折线图实战

    文章目录 专栏导读 1 基本流程 2 多条折线图 3 添加最小值最大值平均值 4 竖线提示信息 5 阶梯图 6 平滑曲线折线图 7 面积折线图 8 堆积图 9 双横坐标折线图 专栏导读 本文已收录于 100天精通Python从入门到就业 本
  • Windows Server 2008 R2 下配置AD证书服务器和HTTPS访问的图文教程 DNS+IIS+AD证书服务

    提前配置好虚拟机和客户机的访问 保证能ping通虚拟机 虚拟机和客户机网络的DNS填写为虚拟机的ip地址 接下来配置好DNS服务 配置域名解析服务 这个用于域名解析 不需要域名访问的可以不配置DNS 使用IP访问即可 安装DNS 进入DNS
  • (ros/qt报错) FATAL: ROS_MASTER_URI is not defined in the environment

    安装qt之后 明明打开roscore但是qt运行跟ros有关的节点时报错 FATAL 1450943695 306401842 ROS MASTER URI is not defined in the environment Either
  • 哪些 Java 知识不需要再学了

    张无忌在学太极拳的时候 他爹的师父张三丰告诫他一定要把之前所学习的武功全部忘掉 忘得越多就会学得越快 同样的 自学 Java 的时候一定要先知道哪些 Java 知识不需要再学了 毕竟技术的更新迭代就好像火箭一样快 Java 的一些知识点早已
  • OJ:algorithm头文件中sort函数的应用

    include
  • 【单片机笔记】STM32+ESP8266通过AT指令WIFI连接阿里云MQTT服务器

    上一篇使用USB转串口的方式通过ESP8266wifi模块的方式成功连接上了阿里云 现在就要通过单片机来替换电脑上位机了 这样单片机自动的去调用并发送串口数据更加方便 也更加符合一个产品的开发 板载的传感器有NTC温度 光强 这两个主要用来
  • 100流明相当于多少w_中艺光影秀揭秘一场大型灯光秀究竟要耗费多少电量呢?...

    炫酷的灯光秀 让城市景区的夜色被赋予了无限可能性 现代的灯光秀始于法国里昂 然而 现代技术的飞速发展 现在的灯光秀的技术也是越玩越炫丽 越来越复杂 在大家纷纷对灯光秀表示震撼的同时 也有一些人质疑 举办这么大规模的灯光秀 太 费 电 了 某
  • [Linux打怪升级之路]-文件操作

    前言 作者 小蜗牛向前冲 名言 我可以接受失败 但我不能接受放弃 如果觉的博主的文章还不错的话 还请点赞 收藏 关注 支持博主 如果发现有问题的地方欢迎 大家在评论区指正 目录 一 认识操纵系统下的文件 1 什么是文件 2 文件的类型 3
  • 医疗器械相关展会

    1 CMEF中国国际医疗器械博览会 始创于1979年 每年春秋两届 已成为亚太地区最大的医疗器械及相关产品 服务展览会
  • 机器学习实战项目总结

    1 预测模型新项目模板 机器学习是针对数据进行自动挖掘 找出数据的内在规律 并应用这个规律来预测新数据 一个很好的实践机器学习项目的方法 使用从UCI机器学习仓库链接 获取的数据集开启一个机器学习项目 分类或回归模型的机器学习项目可以分成以
  • Linux Nginx配置静态html

    Linux Nginx配置静态html 第一次接触服务器时 对项目部署和域名部署感觉很新鲜 有意思 很神奇 开始学习Nginx配置静态的HTML 使用工具 MobaXterm CHS exe 第一步 将html的项目放入服务器 将自己做好的
  • 【华为OD机试真题 JAVA】信道分配

    JS版 华为OD机试真题 JS 信道分配 标题 信道分配 时间限制 1秒 内存限制 262144K 语言限制 不限 算法工程师小明面对着这样一个问题 需要将通信用的信道分配给尽量多的用户 信道的条件及分配规则如下 1 所有信道都有属性 阶
  • origin中把多个拟合曲线放在一张图

    双击其中一个 或者New一个graph 这里直接双击其中一个图 右键 找到layer contents 可以看到 一个scatter配一个polynomial fit line 把剩下的B C D对应的点和拟合曲线添加进去 注意 这里的pl
  • 01、Java并发 Java ExecutorService

    ExecutorService 是 Java java util concurrent 包的重要组成部分 是 Java JDK 提供的框架 用于简化异步模式下任务的执行 一般来说 ExecutorService 会自动提供一个线程池和相关