当前位置: 首页 > news >正文

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里面拿到执行结果。

相关文章:

Java线程池execute和submit的区别

前言 ThreadPoolExecutor提供了两种方法来执行异步任务&#xff0c;分别是execute和submit&#xff0c;也是日常开发中经常使用的方法&#xff0c;那么它俩有什么区别呢&#xff1f; 语义不同 首先是语义上的不同。execute声明在Executor接口&#xff0c;它接受一个Runnable…...

什么是json

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它基于JavaScript编程语言的一个子集&#xff0c;但是由于其文本格式清晰、易于解析&#xff0c;并且能够以键/值对的形式表示复杂的数据结构&#xff0c;因此它被广泛用于不同的编程语言和…...

基于聚类和回归分析方法探究蓝莓产量影响因素与预测模型研究附录

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 目录 背景数据说明数据来源思考 附录数据预处理导入包以及数据读取数据预览数据处理 相关性分析聚类分析数据处理确定聚类数建立k均值聚类模型 …...

java类型转换

pom <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency>BeanUtils 在这里插入代码片list<Map>转换成List<bean> public static <T> L…...

Unity打包Webgl端进行 全屏幕自适应

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一&#xff1a;修改 index.html二&#xff1a;将非移动端设备&#xff0c;canvas元素的宽度和高度会设置为100%。三&#xff1a;修改style.css总结 下载地址&#x…...

36. 【Java教程】输入输出流

本小节将会介绍基本输入输出的 Java 标准类&#xff0c;通过本小节的学习&#xff0c;你将了解到什么是输入和输入&#xff0c;什么是流&#xff1b;输入输出流的应用场景&#xff0c;File类的使用&#xff0c;什么是文件&#xff0c;Java 提供的输入输出流相关 API 等内容。 1…...

Visual C++2010学习版详细安装教程(超详细图文)

Visual C 介绍 Visual C&#xff08;简称VC&#xff09;是微软公司推出的一种集成开发环境&#xff08;IDE&#xff09;&#xff0c;主要用于开发C和C语言的应用程序。它提供了强大的编辑器、编译器、调试器、库和框架支持&#xff0c;以及丰富的工具和选项&#xff0c;使得开…...

matlab图像处理入门

matlab在学校科研&#xff0c;仿真及基于模型开发的工作中有重要作用&#xff0c;在图像处理方面由于省去了复杂的上位机开发流程&#xff0c;因此可以让用户快速开发验证算法&#xff0c;下面简要介绍其在图像处理方面的应用。 matlab开发图像处理算法的流程主要是&#xff0c…...

关于线程池面试题,使用“豆包”训练答案

我提问&#xff1a; 问题描述 下面是一个有关线程池调度的面试真题&#xff0c;来自于疯狂创客圈社群&#xff1a; 一个线程池的核心线程数为10个&#xff0c;最大线程数为20个&#xff0c;阻塞队列的容量为30。现在提交45个 任务&#xff0c;每个任务的耗时为500毫秒。 请问&…...

【WRF理论第二期】模型目录介绍

WRF理论第二期&#xff1a;模型目录介绍 1 WRF主目录2 WPS主目录3 编译后的可执行文件4 运行目录参考 了解 WRF 模型的目录结构有助于有效地管理和操作模型&#xff0c;从而确保模拟和分析工作的顺利进行。以下分解介绍WRF主目录、WPS主目录等。 Github-wrf-model/WRF 1 WRF…...

从了解到掌握 Spark 计算框架(一)Spark 简介与基础概念

文章目录 什么是 Spark&#xff1f;核心特点 Spark 对比 MapReduceSpark 编程模型RDDDataFrameDataset Spark 运行模式Spark 生态 什么是 Spark&#xff1f; Spark 是一个基于内存的分布式计算框架&#xff0c;最初由加州大学伯克利分校的 AMPLab 开发&#xff0c;后来捐赠给了…...

linux bind函数

bind函数的目的是让把客户端对应的端口(port)地址和ip地址绑定到客户端 [参考](Linux之bind 函数&#xff08;详细篇&#xff09;_linux bind函数-CSDN博客)...

Flink系列一:flink光速入门 (^_^)

引入 spark和flink的区别&#xff1a;在上一个spark专栏中我们了解了spark对数据的处理方式&#xff0c;在 Spark 生态体系中&#xff0c;对于批处理和流处理采用了不同的技术框架&#xff0c;批处理由 Spark-core,SparkSQL 实现&#xff0c;流处理由 Spark Streaming 实现&am…...

PySpark特征工程(III)--特征选择

有这么一句话在业界广泛流传&#xff1a;数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已。由此可见&#xff0c;特征工程在机器学习中占有相当重要的地位。在实际应用当中&#xff0c;可以说特征工程是机器学习成功的关键。 特征工程是数据分析…...

Mongodb的数据库简介、docker部署、操作语句以及java应用

Mongodb的数据库简介、docker部署、操作语句以及java应用 本文主要介绍了mongodb的基础概念和特点&#xff0c;以及基于docker的mongodb部署方法&#xff0c;最后介绍了mongodb的常用数据库操作语句&#xff08;增删改查等&#xff09;以及java下的常用语句。 一、基础概念 …...

七大战略性新兴产业崭露头角:新能源电燃灶或将成为未来厨房新宠

近日&#xff0c;在国家发布的七大战略性新兴产业名单中&#xff0c;新能源产业赫然在列&#xff0c;作为其中的重要组成部分&#xff0c;华火新能源电燃灶凭借其独特的优势&#xff0c;正逐渐走进人们的视野&#xff0c;有望成为未来厨房的新宠。 华火新能源电燃灶作为清洁能源…...

C#进阶-用于Excel处理的程序集

在.NET开发中&#xff0c;处理Excel文件是一项常见的任务&#xff0c;而有一些优秀的Excel处理包可以帮助开发人员轻松地进行Excel文件的读写、操作和生成。本文介绍了NPOI、EPPlus和Spire.XLS这三个常用的.NET Excel处理包&#xff0c;分别详细介绍了它们的特点、示例代码以及…...

持续总结中!2024年面试必问 20 道 Kafka面试题(五)

上一篇地址&#xff1a;持续总结中&#xff01;2024年面试必问 20 道 Kafka面试题&#xff08;四&#xff09;-CSDN博客 九、请解释Kafka中的Zookeeper的作用。 在Kafka中&#xff0c;ZooKeeper扮演着至关重要的角色&#xff0c;主要负责集群管理、协调和状态同步等功能。以下…...

Draw.io 使用详细教程

Draw.io 是一款功能强大的在线绘图工具&#xff0c;适用于创建流程图、网络图、组织结构图、UML 图等。以下是详细的使用教程&#xff0c;包括基本操作、快捷键、常用技巧和进阶技巧。 1. 创建新图 选择存储位置 首次使用时&#xff0c;系统会询问你要将图保存到哪里。你可以…...

人工智能学习笔记(1):了解sklearn

sklearn 简介 Sklearn是一个基于Python语言的开源机器学习库。全称Scikit-Learn&#xff0c;是建立在诸如NumPy、SciPy和matplotlib等其他Python库之上&#xff0c;为用户提供了一系列高质量的机器学习算法&#xff0c;其典型特点有&#xff1a; 简单有效的工具进行预测数据分…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

针对药品仓库的效期管理问题,如何利用WMS系统“破局”

案例&#xff1a; 某医药分销企业&#xff0c;主要经营各类药品的批发与零售。由于药品的特殊性&#xff0c;效期管理至关重要&#xff0c;但该企业一直面临效期问题的困扰。在未使用WMS系统之前&#xff0c;其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...

Vue3 PC端 UI组件库我更推荐Naive UI

一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用&#xff0c;前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率&#xff0c;还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库&#xff08;Naive UI、Element …...