Java线程池execute和submit的区别

前言

ThreadPoolExecutor提供了两种方法来执行异步任务,分别是execute和submit,也是日常开发中经常使用的方法,那么它俩有什么区别呢?

语义不同

首先是语义上的不同。execute声明在Executor接口,它接受一个Runnable类型的入参,且没有返回值,代表它可以异步执行一个没有返回结果的异步任务,你甚至不知道这个任务什么时候执行完毕。

public interface Executor {void execute(Runnable command);
}

而submit声明在ExecutorService,且有多个重载方法,最明显的区别就是submit方法均有Future返回值。Future代表未来结果的一个占位符,可以通过它拿到异步任务的执行结果,如果异步任务执行失败,也可以通过它拿到异常信息。
所以,下面三个方法的区别是:方法1的入参是Runnable没有返回结果,所以返回的Future是也拿不到结果的,只能判断异步任务是否执行完毕以及是否执行异常;方法2返回的Future可以拿到结果,值是第二个入参;方法3也可以拿到结果,值就是入参Callable返回的结果。

public interface ExecutorService extends Executor {[1] Future<?> submit(Runnable task);[2] <T> Future<T> submit(Runnable task, T result);[3] <T> Future<T> submit(Callable<T> task);
}

综上所述,execute和submit最明显的一个区别就是语义上的不同。execute用来执行没有返回结果的异步任务,且调用方不关心任务的执行结果;而submit用来执行有返回结果的异步任务,适用于调用方关心执行结果和异步任务执行有返回值的场景。

异常处理不同

execute和submit第二个区别是:异步任务执行异常时的处理不同。execute遇到异常会直接抛出来,而submit会默默吃掉异常。
如下示例,execute会把除0异常的堆栈打印出来,而submit则没有打印任何信息。

public static void main(String[] args) throws Exception {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());executor.execute(() -> {System.err.println("execute result:");System.err.println(1 / 0);});executor.submit(() -> {System.err.println("submit result:");System.err.println(1 / 0);});}
execute result:
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zeroat ExecutorDemo2.lambda$main$0(ExecutorDemo2.java:18)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:840)
submit result:

你可能会觉得疑惑,submit凭什么要默默吃掉异常,任务执行出错为什么不通知调用方呢?
事实上,submit针对异常的处理方式正和它的语义有关,它并没有吃掉异常,而是它认为方法执行异常也是结果的一部分,别忘了它是有Future返回值的,只不过它并没有粗暴的直接抛出异常,而是把异常记录在了Future返回值里面。如果要关心submit提交的异步任务的执行情况,可以通过下面这种方式:

Future<?> future = executor.submit(() -> {System.err.println("submit result:");System.err.println(1 / 0);
});
try {future.get();System.err.println("正常执行");
} catch (Exception e) {System.err.println("原来执行异常了:" + e.getMessage());
}

异常线程销毁重建

execute和submit第三个区别是:execute面对异常线程会销毁重建,而submit会继续复用异常线程。
看下面的例子,创建一个固定3个线程的线程池,先分别execute执行2个正常的任务和1个异常任务,sleep一会再次执行三个任务,我们发现3号线程因为执行异常被销毁了,线程池重新启动了4号线程。如果调用submit,则始终是一开始创建的三个线程在工作,不会出现线程的销毁和重建情况。

public static void main(String[] args) throws Exception {ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());executor.execute(new Task(false));executor.execute(new Task(false));executor.execute(new Task(true));Thread.sleep(1000);System.err.println("===============");for (int i = 0; i < 3; i++) {executor.execute(new Task(false));}
}static class Task implements Runnable {private boolean throwException;public Task(boolean throwException) {this.throwException = throwException;}@SneakyThrows@Overridepublic void run() {if (throwException) {System.err.println("error:" + Thread.currentThread().getName());throw new RuntimeException();}Thread.sleep(1);System.err.println("task:" + Thread.currentThread().getName());}
}
error:pool-1-thread-3
task:pool-1-thread-2
task:pool-1-thread-1
Exception in thread "pool-1-thread-3" java.lang.RuntimeExceptionat ExecutorDemo$Task.run(ExecutorDemo.java:36)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:840)
===============
task:pool-1-thread-1
task:pool-1-thread-4
task:pool-1-thread-2

源码浅析

最后,从源码层面看看导致execute和submit区别的原因。
execute方法道尽了线程池的工作流程,如果工作线程数小于核心线程数,面对提交的任务会创建新线程去执行,否则尝试入队,队列满则继续创建线程直到最大线程数。

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {// 小于核心线程数,启动新线程if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);
}

在线程池里,线程会被封装成Worker对象,它的run方法是个while循环,不停的从任务队列workQueue里面取出异步任务并执行,不过它在执行任务时,如果遇到异常,会直接抛出来。并且在finally里面会把异常线程从workers里面移除,后续当工作线程数小于核心线程数时又会继续创建新的线程。

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock();boolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex; // 直接抛出异常}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}
}

而submit之所以不会抛出异常,是因为它对提交的任务进行了一层封装,Runnable封装成了FutureTask:

public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;
}

FutureTask就是Future的实现类,它重写了run方法,对异常进行了捕获,如果发生异常会把异常记录下来,而不是直接抛出,调用者可以通过Future来获得异常信息。

public void run() {if (state != NEW ||!RUNNER.compareAndSet(this, null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {// 捕获异常result = null;ran = false;setException(ex);// 记录异常}if (ran)set(result);}} finally {runner = null;int s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}
}

尾巴

execute和submit都可以执行异步任务,但是有三大区别,分别是:1.语义上的区别,是否关心任务的执行情况和返回结果;2.遇到遇到是直接抛出还是先记录下来;3.异常线程是销毁重建还是继续复用。在源码层面,本质上submit只是针对提交的任务又做了一层包装,FutureTask重写了run()捕获了异常信息并记录了下来,方便调用者从Future里面拿到执行结果。

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

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

相关文章

【论文阅读】Point2RBox (CVPR’2024)

paper:https://arxiv.org/abs/2311.14758 code:https://github.com/yuyi1005/point2rbox-mmrotate

阿里云sls 采集日志安装记录

参考阿里云给的安装文档 阿里云安装Logtail组件 注意这里&#xff0c;选择地域&#xff0c;是中国地域选中国&#xff0c;海外选海外即可 按照文档继续下去 修改配置文件./alibaba-cloud-log-all/values.yaml 所有的操作完成后&#xff0c;去控制台配置 以上操作的前提是…

[WWW2024]轻量数据依赖的异常检测重训练方法LARA

开篇 近日&#xff0c;由阿里云计算平台大数据基础工程技术团队主导&#xff0c;与浙江大学合作的论文《LARA: ALight and Anti-overfitting Retraining Approach for Unsupervised Time Series Anomaly Detection 》被WWW2024收录&#xff0c;该方法解决了云服务正常模式随时…

探索AIGC降重工具:确保论文原创性的新策略

如何有效降低AIGC论文的重复率&#xff0c;也就是我们说的aigc如何降重&#xff1f;AIGC疑似度过高确实是个比较愁人的问题。如果你用AI帮忙写了论文&#xff0c;就一定要在交稿之前做一下AIGC降重的检查。一般来说&#xff0c;如果论文的AIGC超过30%&#xff0c;很可能会被判定…

申请医疗设备注册变更时,需要补充考虑网络安全的情况有哪些?

在申请医疗器械设备注册变更时&#xff0c;需要补充网络安全的情况主要包括以下几点&#xff1a; 网络安全功能更新&#xff1a;如果医疗器械的自研软件发生网络安全功能更新&#xff0c;或者合并网络安全补丁更新的情形&#xff0c;需要单独提交一份自研软件网络安全功能更新…

#02 安装指南:如何配置Stable Diffusion环境

文章目录 前言前置条件第1步&#xff1a;安装Python和PIP第2步&#xff1a;创建虚拟环境第3步&#xff1a;安装PyTorch和CUDA第4步&#xff1a;安装Stable Diffusion相关库第5步&#xff1a;测试环境结论 前言 在之前的文章中&#xff0c;我们介绍了Stable Diffusion基础入门和…

【ARFoundation自学04】AR Tracked Image 图像追踪识别

1.添加组件 2.创建图像识别库 3.创建识别后追踪的物体&#xff08;UI、模型等&#xff09;

Java驱动的工程项目管理系统:实现高效协作与精准管理

在工程行业的现代管理实践中&#xff0c;有效地协同工作和信息共享对于提高工作效率和降低成本至关重要。本文将深入探讨一款基于Java技术的工程项目管理系统&#xff0c;该系统采用前后端分离的架构&#xff0c;功能全面&#xff0c;旨在满足不同角色的需求&#xff0c;从项目…

go语言切片去重的3种方式总结

go语言中的切片是使用非常频繁的一个数据结构&#xff0c;对于他的去重&#xff0c;我们可以有以下3种方式 1. 切片slice去重 利用map的key不能重复的特性append函数 一次for循环搞定 这个模式时间复杂度最低&#xff0c;效率最高&#xff0c; 如果go版本大于1.21推荐使用这…

PlugLink与RPA的完美结合:打造智能自动化工作流(附源码)

PlugLink与RPA的完美结合&#xff1a;打造智能自动化工作流 自动化技术已经成为提高效率和减少错误的关键手段。两种主要的自动化技术——PlugLink和RPA&#xff08;机器人流程自动化&#xff09;——各有特色。本文将详细探讨PlugLink与RPA的不同之处&#xff0c;并介绍它们如…

软件测试需求管理指南规范(Word原件,项目管理全资料)

3 测试需求 3.1 测试范围 3.2 测试目标 4 测试需求的现状 5 测试需求的内容 5.1 主体内容 5.2 管理内容 6 测试需求的制定 6.1 需求信息来源 6.2 需求分析 6.2.1 功能性需求 6.2.2 系统功能需求 6.2.3 界面需求 6.2.4 安装需求 6.2.5 业务需求 6.2.6 非功能性需求 6.2.7 性能需…

ai怎么导出jpg?让我告诉你答案【详】

在设计和创意工作中&#xff0c;Adobe Illustrator&#xff08;AI&#xff09;是一款不可或缺的工具。然而&#xff0c;当我们将设计作品导出为JPG格式时&#xff0c;可能会遇到一些问题。ai怎么导出jpg&#xff1f;如何确保导出的JPG图片保持高质量&#xff1f;接下来&#xf…

异步通知驱动实例

目录 异步通知驱动功能的优点 异步通知测试程序: 异步驱动程序 异步通知驱动功能的优点 提高性能:通过允许应用程序在等待操作完成时执行其他任务,可以提高应用程序的整体性能。改善用户体验:应用程序可以保持响应性,用户界面可以流畅地更新,提供更好的用户体验。资源…

【Js】深入浅出的js for循环 for loop以及闭坑指南

在JavaScript中使用forEach循环来删除数组中的特定元素可能会导致一些问题&#xff0c;因为forEach不允许你在迭代过程中修改数组的长度。 这会导致意外的行为&#xff0c;例如跳过元素或错误地索引。因此&#xff0c;建议使用其他方法来安全地删除数组中的元素。 存在的问题 1…

php质量工具系列之phpmd

PHPMD PHP Mess Detector 它是PHP Depend的一个衍生项目&#xff0c;用于测量的原始指标。 PHPMD所做的是&#xff0c;扫描项目中可能出现的问题如&#xff1a; 可能的bug次优码过于复杂的表达式未使用的参数、方法、属性 PHPMD是一个成熟的项目&#xff0c;它提供了一组不同的…

HarmonyOS NEXT Push接入

接入HarmonyOS NEXT Push 推送功能,相比于 Android 真的是简单太多。不再需要适配接入各个厂家的推送 SDK,真是舒服。 1.开通推送服务与配置Client ID 1.1 创建应用获取Client ID 按照官方文档来就可以了:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides…

常用的接口测试工具

大家好&#xff0c;当谈到软件开发中的质量保证时&#xff0c;接口测试无疑是至关重要的一环。在当今快节奏的开发环境中&#xff0c;确保应用程序的各个组件之间的交互正常运作是至关重要的。而接口测试工具则成为了开发人员和测试人员的得力助手&#xff0c;帮助他们有效地测…

关于抽样检验的案例来说明95%置信区间

抽样检验是统计学中常用的一种方法&#xff0c;通过分析样本数据来推断总体特征。95%置信区间在抽样检验中扮演着重要角色&#xff0c;因为它提供了对总体参数估计的可靠性区间。下面是一个具体的案例来说明如何使用95%置信区间进行抽样检验。 案例背景 假设一家制药公司正在…

LLM推理加速原理(一)

1.大语言模型的基本结构 transfomer block: 输入--->正则化-->qkv三个矩阵层(映射到三个不同空间中)---->q,k,v之后self attention进行三0合一---->线性映射,正则化。 2.大语言模型的推理 目前主流的语言大模型都采用decoder-only的结构,其推理过程由两部分…

YOLOv3训练自己的数据集

简要笔记 一、数据标注 1.安装好labelme工具 2.标注自己的数据&#xff0c;拿到json文件 二、编辑训练代码所需要的配置文件 config文件夹&#xff0c;create_custom_model.sh &#xff08;参数&#xff1a;类别个数&#xff09; 自动生成网络cfg文件 三、标签格式转换 1.la…