线程池中execute与submit的区别说明
作者:每年进步一点点 发布时间:2023-03-18 23:09:04
线程池execute与submit区别
在使用线程池的时候,看到execute()与submit()方法。都可以使用线程池执行一个任务,但是两者有什么区别呢?
execute
void execute(Runnable command);
submit
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
相同点:
1 都可以执行任务
2 参数都支持runnable
不同点:
1 submit支持接收返回值 详见例1。
2 execute 任务里面的异常必须捕获,不能向上抛出;submit支持的Callable支持向上抛出异常,需要由返回值.get()来进行接收。详见例2。
例1
public class ExecutorTest {
public static ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
Future<?> result1 = executorService.submit(new Callable() {
@Override
public Object call() throws Exception {
TimeUnit.SECONDS.sleep(10);
System.out.println("Thread1 sleep 10 seconds");
return true;
}
});
Future<?> result2 = executorService.submit(new Callable() {
@Override
public Object call() throws Exception {
TimeUnit.SECONDS.sleep(5);
System.out.println("Thread2 sleep 5 seconds");
return false;
}
});
try {
System.out.println("Thread1 return:"+result1.get());
System.out.println("Thread2 return:"+result2.get());
System.out.println("finished");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
该例子的返回结果:
Thread2 sleep 5 seconds
Thread1 sleep 10 seconds
Thread1 return:true
Thread2 return:false
finished
解释:可以看到接收到了两个线程的返回结果。利用thread.sleep来模拟耗时操作,直到两个线程执行完毕之后,才会输出finished。利用Callable的返回阻塞,来等待这n个线程的执行完毕,然后将这n个线程的结果响应回去;其执行时间,基本上取决于最耗时的那个线程。
适用场景:在某些情况下,需要获得一组线程的结果,给调用端。
例2
而execute不支持向上抛出异常,必须将异常捕获。
线程池submit和execute方法原理
线程池的作用
1. 避免大量的线程强占资源
2. 避免大量的线程创建和销毁带来的开销
线程池的原理
创建线程池的时候,可以使用executors的静态方法,也可以使用new ThreadPoolExecutor的方式手动创建线程池,通过在线程池中指定参数达到创建不同类型的线程池的效果
其中,executors底层其实也是调用的new ThreadPoolExecutor()的方式创建的,是对不同线程池的封装,
线程的执行有两种方式,一种是submit(runnable v)的形式,一种是execute(runnable b) 的形式,不同的是submit可以返回一个future的实现类,相同的一点是submit底层其实也是调用的execute
调用execut方法,首先判断传入的参数是否为空,如果为空,抛出异常,如果不为空,使用获取ctl值,计算出当前线程状态码,通过状态码计算出当前线程池工作线程是否小于核心线程数量
如果小于,判断添加工作线程操作是否正常,如果正常,直接返回,如果不正常,继续执行获取ctl值,在添加工作线程的过程中,首先通过循环的方式保证ctl在加1的情况下状态同步,如果不同步,一直循环到同步为止,添加完成后,创建线程工作对象,把工作线程添加到set集合中,并执行.start,如果执行不成功,从set中删除添加的worker对象,并且ctl回滚到之前没有自增的值.
如果上述中添加工作线程失败,或者当前线程池中工作线程数量操作和信息数量,执行下列逻辑
判断当前线程池状态是否是running状态:
如果不是running状态,或者是running状态,并且添加到线程队列失败,重新添加个工作线程,此时入参中第二个参数用于添加工作线程的逻辑中当前工作线程数量与最大线程数量做对比,如果添加失败,执行reject处理类处理
如果是running状态,并且添加队列成功,重新获取ctl值,判断当前线程池状态如果是不是running状态,并且从对象中删除成功,则当前线程交给拒绝线程处理器处理,如果不满足上面条件,判断当前线程池的工作线程数如果为0,重新添加一个不带任务的线程.
//AbstractExecutorService.java文件
// executorService 中的 submit 方法
public Future<?> submit(Runnable task) {
// 首先判断传入的runnable 对象是否为空
if (task == null) throw new NullPointerException();
// 创建一个 futuretask 对象
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// 根据runnable 创建一个futuretask对象
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// ThreadPoolExecutor.java文件
// 执行创建线程池的方法
public void execute(Runnable command) {
// 首先判断传入的线程是否为空
if (command == null)
// 为空,抛出异常
throw new NullPointerException();
// 获取线程池的状态码, 这个状态码是自增的,原子类型的自增, 在执行addworker后ctl会加1
int c = ctl.get();
// 通过状态码,获取线程池中的线程的数量,如果小于核心数量
if (workerCountOf(c) < corePoolSize) {
// 添加线程到线程池,并且为true时使用核心线程数作为边界,如果false ,使用最大数量线程数作为边界
if (addWorker(command, true))
// 添加完成后,返回
return;
// 如果添加失败,重新获取状态值
c = ctl.get();
}
// 执行下面逻辑有两种情况
// 1. 工作线程数大于核心线程
// 2. 添加线程时出错
// 如果线程池中线程的数量大于核心的数量, 判断如果是运行状态, 并且也把线程加进了阻塞队列 workQueue 中
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取 线程池 状态值
int recheck = ctl.get();
// 判断当前线程池如果不是运行状态,并且成功从队列中移除(从workQueue中移除线程, 并尝试终止线程池)
if (! isRunning(recheck) && remove(command))
// 执行拒绝执行线程的处理
reject(command);
// 如果工作线程数为0
else if (workerCountOf(recheck) == 0)
// 添加一个null的工作包装对象
addWorker(null, false);
} else if (!addWorker(command, false))
// 如果添加到线程池中出错,执行拒接的线程
reject(command);
}
// 创建一个原子类对象用于计算线程的中状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// integer.size 为 32
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 即高3位为1,低29位为0,该状态的线程池会接收新任务,也会处理在阻塞队列中等待处理的任务
private static final int RUNNING = -1 << COUNT_BITS;
// 即高3位为0,低29位为0,该状态的线程池不会再接收新任务,但还会处理已经提交到阻塞队列中等待处理的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 即高3位为001,低29位为0,该状态的线程池不会再接收新任务,不会处理在阻塞队列中等待的任务,而且还会中断正在运行的任务
private static final int STOP = 1 << COUNT_BITS;
// 即高3位为010,低29位为0,所有任务都被终止了,workerCount为0,为此状态时还将调用terminated()方法
private static final int TIDYING = 2 << COUNT_BITS;
// 即高3位为100,低29位为0,terminated()方法调用完成后变成此状态
private static final int TERMINATED = 3 << COUNT_BITS;
// 用户计算线程的状态 32位中 高3位为1 低29位为0
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 用于计算线程池中线程的数量 32位中 高3位为0 低29位为1
private static int workerCountOf(int c) { return c & CAPACITY; }
// rs 为 runState, wc 为 workerCount 通过工作状态和线程数量来计算出 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 添加工作线程的方法
private boolean addWorker(Runnable firstTask, boolean core) {
// 设置循环跳出点,如果执行到某个位置,使用break,直接跳出的是这个标签范围内的所有循环
retry:
for (;;) {
// 获取线程状态
int c = ctl.get();
int rs = runStateOf(c);
// 判断线程池状态是否在shutdown上以及 状态不是关闭并且添加的线程不为空,并且线程队列中的线程不是空的
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
// 如果满足上面条件,说明线程池已经不适合添加新的线程了, 直接返回false
return false;
// 如果不满足上面条件,说明线程池可以添加线程, 下面这个循环主要是对ctl进行操作,保证在增1后线程状态保持同步
for (;;) {
// 获取工作线程数量
int wc = workerCountOf(c);
// 判断当前线程池中工作线程数量是否大于线程容量,大于核心线程数或最大线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
// 满足条件,说明当前线程不是适合添加新的线程的
return false;
// 如果工作数量少于最大量或者核心线程数或最大线程数, 工作线程数加1,即操作ctl,通过cas的方式
if (compareAndIncrementWorkerCount(c))
// 如果添加成功,跳出内循环,
break retry;
// 如果添加失败,重新获取ctl
c = ctl.get(); // Re-read ctl
// 判断此时线程池状态是否已经改变
if (runStateOf(c) != rs)
//如果状态不一致,跳过,重新循环
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
// 创建一个线程包装对象,用于包装线程
Worker w = null;
try {
w = new Worker(firstTask);
// 创建一个worker 工作线程
final Thread t = w.thread;
// 判断创建的线程是否为空
if (t != null) {
// 如果不为空,获取锁对象
final ReentrantLock mainLock = this.mainLock;
// 开始加锁
mainLock.lock();
try {
// 获取线程池状态
int rs = runStateOf(ctl.get());
// 如果线程池状态是running或者线程池状态关闭并且传入的线程是空的
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 判断创建的工作线程是否是活动状态(已经开始还没有死掉)
if (t.isAlive()) // precheck that t is startable
// 如果是活动状态,抛出 非法线程状态异常
throw new IllegalThreadStateException();
// 如果不是活动状态, 添加到set集合中,这个set集合只有持有mainlock才可以访问
workers.add(w);
// 获取集合长度
int s = workers.size();
// 如果存放刚才创建的workers工作线程的集合中的线程数超过最大的池的大小
if (s > largestPoolSize)
// 把set集合中的数量代替原线程池最大值
largestPoolSize = s;
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
// 根据前面的判断是否需要开启线程,如果线程已经是活动的,不需要开启,如果不是活动线程,开启线程
if (workerAdded) {
t.start();
// 开启成功,设置workerStarted 为 true
workerStarted = true;
}
}
} finally {
// 如果工作线程开启失败,调用添加到失败的线程中
if (! workerStarted)
// 从set中移除失败的线程,并且ctl减1, 并且尝试终止线程池
addWorkerFailed(w);
}
return workerStarted;
}
// 线程开启失败后的方法
private void addWorkerFailed(Worker w) {
// 获取锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
// 如果线程不为空,从set集合中移除没有开启成功的线程
workers.remove(w);
// 减去之前ctl增加的1
decrementWorkerCount();
// 尝试中断线程
tryTerminate();
} finally {
mainLock.unlock();
}
}
// 通过cas方式ctl加1
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
// 移除线程
public boolean remove(Runnable task) {
// 从等待队列中一尺线程
boolean removed = workQueue.remove(task);
// 尝试终止线程池
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
// 使用拒绝处理对象执行拒接指定线程
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
来源:https://blog.csdn.net/qq_38008721/article/details/103809974
猜你喜欢
- 目前为止我们已经了解了如何通过编程创建 CompletableFuture 对象以及如何获取返回值,虽然看起来这些操作已经比较方便,但还有进
- 本文实例为大家分享了Android开发之自定义闹钟实现,供大家参考,具体内容如下闹钟时间设置及显示闹钟的布局很简单,就是一个简单时间设置,所
- 需求描述现在有这样一个需求:我有A、B两台服务器,其中A是一个视频处理服务器,B是一个数据存储服务器。此时有一个视频需要先在A服务器上进行一
- 本文实例讲述了C#数字图像处理之图像二值化(彩色变黑白)的方法。分享给大家供大家参考。具体如下://定义图像二值化函数private sta
- 概述透视表是依据已有数据源来创建的交互式表格,我们可在excel中创建透视表,也可编辑已有透视表。所需工具:Free Spire.XLS f
- 本文实例讲述了Android开发中4个常用的工具类。分享给大家供大家参考,具体如下:1、土司工具类(Toast管理)/** * Toast统
- 今天给大家讲讲 SpringBoot 框架 整合 Elasticsearch 实现海量级数据搜索。一、简介在上篇ElasticSe
- 实现说明这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据
- 效果图接下来就是一波贴代码的过程自定义Dialogpublic class SinaSendView extends Dialog { &n
- 1.新建一个项目2.给项目添加引用:Microsoft Excel 12.0 Object Library (2007版本)using Ex
- 对于导航组件的使用方式不是本文的重点,具体使用可以参考官方文档,导航组件框架是通过f
- Android强制异步转同步方法,供大家参考,具体内容如下Android系统中规定耗时任务需要在异步线程中进行,特别是网络请求必须在异步线程
- 条码的应用已深入生活和工作的方方面面。在处理条码时,常需要和各种文档格式相结合。当需要在文档中插入、编辑或者删除条码时,可借助于一些专业的类
- 记录一下使用IDEA创建servlet并使用Tomcat本地部署的过程。需要安装好的软件首先IDEA社区版不支持Java EE,因此要使用U
- WebFlux服务编排WebFlux 服务编排是指使用 WebFlux 框架来编排多个异步服务的执行顺序和数据流动,从而构建出一个完整的、基
- 本文实例总结了C#实现启用与禁用本地网络的方式。分享给大家供大家参考,具体如下:1) 使用Hnetcfg.dll使用Add Referenc
- C语言运算符及其优先级汇总表口诀圆下箭头一顿号非凡增减富强针地长三乘除,四加减,五移位千万别把鱼忘记,它在盛饭的厨子里小灯大灯灯灯不等爸喂鱼
- 背景介绍在开发应用过程中经常会遇到显示一些不同的字体风格的信息犹如默认的LockScreen上面的时间和充电信息。对于类似的情况,可能第一反
- 以下是介绍利用List的subList方法实现对List分页,废话不多说了,直接看代码把/** *//** * List分页 &
- Eureka什么是服务治理为什么需要服务治理?  服务治理是主要针对分布式服务框架的微服务,处理服务调用