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提供了两种方法来执行异步任务,分别是execute和submit,也是日常开发中经常使用的方法,那么它俩有什么区别呢? 语义不同 首先是语义上的不同。execute声明在Executor接口,它接受一个Runnable…...
什么是json
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于JavaScript编程语言的一个子集,但是由于其文本格式清晰、易于解析,并且能够以键/值对的形式表示复杂的数据结构,因此它被广泛用于不同的编程语言和…...

基于聚类和回归分析方法探究蓝莓产量影响因素与预测模型研究附录
🌟欢迎来到 我的博客 —— 探索技术的无限可能! 🌟博客的简介(文章目录) 目录 背景数据说明数据来源思考 附录数据预处理导入包以及数据读取数据预览数据处理 相关性分析聚类分析数据处理确定聚类数建立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端进行 全屏幕自适应
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一:修改 index.html二:将非移动端设备,canvas元素的宽度和高度会设置为100%。三:修改style.css总结 下载地址&#x…...

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

Visual C++2010学习版详细安装教程(超详细图文)
Visual C 介绍 Visual C(简称VC)是微软公司推出的一种集成开发环境(IDE),主要用于开发C和C语言的应用程序。它提供了强大的编辑器、编译器、调试器、库和框架支持,以及丰富的工具和选项,使得开…...
matlab图像处理入门
matlab在学校科研,仿真及基于模型开发的工作中有重要作用,在图像处理方面由于省去了复杂的上位机开发流程,因此可以让用户快速开发验证算法,下面简要介绍其在图像处理方面的应用。 matlab开发图像处理算法的流程主要是,…...
关于线程池面试题,使用“豆包”训练答案
我提问: 问题描述 下面是一个有关线程池调度的面试真题,来自于疯狂创客圈社群: 一个线程池的核心线程数为10个,最大线程数为20个,阻塞队列的容量为30。现在提交45个 任务,每个任务的耗时为500毫秒。 请问&…...

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

从了解到掌握 Spark 计算框架(一)Spark 简介与基础概念
文章目录 什么是 Spark?核心特点 Spark 对比 MapReduceSpark 编程模型RDDDataFrameDataset Spark 运行模式Spark 生态 什么是 Spark? Spark 是一个基于内存的分布式计算框架,最初由加州大学伯克利分校的 AMPLab 开发,后来捐赠给了…...
linux bind函数
bind函数的目的是让把客户端对应的端口(port)地址和ip地址绑定到客户端 [参考](Linux之bind 函数(详细篇)_linux bind函数-CSDN博客)...

Flink系列一:flink光速入门 (^_^)
引入 spark和flink的区别:在上一个spark专栏中我们了解了spark对数据的处理方式,在 Spark 生态体系中,对于批处理和流处理采用了不同的技术框架,批处理由 Spark-core,SparkSQL 实现,流处理由 Spark Streaming 实现&am…...

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

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

七大战略性新兴产业崭露头角:新能源电燃灶或将成为未来厨房新宠
近日,在国家发布的七大战略性新兴产业名单中,新能源产业赫然在列,作为其中的重要组成部分,华火新能源电燃灶凭借其独特的优势,正逐渐走进人们的视野,有望成为未来厨房的新宠。 华火新能源电燃灶作为清洁能源…...
C#进阶-用于Excel处理的程序集
在.NET开发中,处理Excel文件是一项常见的任务,而有一些优秀的Excel处理包可以帮助开发人员轻松地进行Excel文件的读写、操作和生成。本文介绍了NPOI、EPPlus和Spire.XLS这三个常用的.NET Excel处理包,分别详细介绍了它们的特点、示例代码以及…...
持续总结中!2024年面试必问 20 道 Kafka面试题(五)
上一篇地址:持续总结中!2024年面试必问 20 道 Kafka面试题(四)-CSDN博客 九、请解释Kafka中的Zookeeper的作用。 在Kafka中,ZooKeeper扮演着至关重要的角色,主要负责集群管理、协调和状态同步等功能。以下…...
Draw.io 使用详细教程
Draw.io 是一款功能强大的在线绘图工具,适用于创建流程图、网络图、组织结构图、UML 图等。以下是详细的使用教程,包括基本操作、快捷键、常用技巧和进阶技巧。 1. 创建新图 选择存储位置 首次使用时,系统会询问你要将图保存到哪里。你可以…...

人工智能学习笔记(1):了解sklearn
sklearn 简介 Sklearn是一个基于Python语言的开源机器学习库。全称Scikit-Learn,是建立在诸如NumPy、SciPy和matplotlib等其他Python库之上,为用户提供了一系列高质量的机器学习算法,其典型特点有: 简单有效的工具进行预测数据分…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...