【Java并发编程】线程池ThreadPoolExecutor实战及其原理分析

news2024/11/27 6:27:39

4 Executor线程池

4.1 概述

线程发生异常,会被移除线程池,但是如果是核心线程,会创建一个新核心线程;

4.1.1 线程池的好处

  • 降低资源消耗

降低了频繁创建线程和销毁线程开销,线程可重复利用;

  • 提高响应速度
  • 提高线程可管理性

统一对线程分配、调优、监控,线程是系统的稀缺资源;

4.1.2 线程池的执行流程

(1)execute()和submit()的区别

public void execute(Runnable command) // 
public Future<?> submit(Runnable task) // 继承自AbstractExecutorService

都是向线程池提交一个任务,交由线程池执行;
execute()方法是Executor中申明的方法,在ThreadPoolExecutor具体实现;
submit()方法是ExecutorService中申明的方法,在AbstractExecutorService具体实现,实际还是调用execute()方法,可以通过Furture返回执行结果:

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

注意:

  • 提交一个Runnable时,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建新线程。
  • ThreadPoolExecutor相当于是非公平的,比如队列满了之后提交的Runnable可能会比正在排队的Runnable先执行。

(2)execute()执行步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dagLSefl-1690028034302)(https://cdn.nlark.com/yuque/0/2023/png/685320/1690018771113-f7ec938c-2d05-4b0a-acd0-d17548186e52.png#averageHue=%23f8f6f4&clientId=uf84c8fa0-3ec2-4&from=paste&height=603&id=ue42c35f4&originHeight=1206&originWidth=1364&originalType=binary&ratio=2&rotation=0&showTitle=false&size=160292&status=done&style=none&taskId=u438e92f1-59da-4120-852d-8d3798f3135&title=&width=682)]

代码分析:

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();
        }
    	// 线程数不小于核心线程数,offer()添加任务到任务队列
        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);
        }
        // 任务队列满了,添加任务队列失败,继续addWorker()判断线程池最大线程数
        else if (!addWorker(command, false))
            // 拒绝任务
            reject(command);
    }
private boolean addWorker(Runnable firstTask, boolean core) // 该方法为添加任务到线程池,其中有判断corePoolSize和maximumPoolSize逻辑

4.1.3 线程池的状态

  • RUNNING(运行中)

正常状态,会接收新任务,会处理等待队列任务;

  • SHUTDOWN(关闭)

不会接收新任务,会处理等待队列任务;

  • STOP(停止)

不会接收新任务,不会处理等待队列任务,中断正在执行的任务;

  • TIDYING(整齐)

所有线程都销毁了,workCount为0,线程池状态在转为TIDYING时会执行钩子方法terminated()方法;

  • TERMINATED(结束)

terminated()方法结束后的状态;

这五种状态并不能任意转换,只会有以下几种转换情况:
RUNNING -> SHUTDOWN:手动调用shutdown()触发,或者线程池对象GC时会调用finalize()从而调用shutdown()
(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()触发,如果先调shutdown()紧着调shutdownNow(),就会发生SHUTDOWN -> STOP
SHUTDOWN -> TIDYING:队列为空并且线程池中没有线程时自动转换
STOP -> TIDYING:线程池中没有线程时自动转换(队列中可能还有任务)
TIDYING -> TERMINATED:terminated()执行完后就会自动转换

4.1.4 线程池为什么要用阻塞队列

线程池中的线程在运行过程中,执行完创建线程时绑定的第一个任务后,就会不断的从队列中获取任务并执行,那么如果队列中没有任务了,线程为了不自然消亡,就会阻塞获取队列任务,等着队列中有任务过来就会拿到任务从而去执行任务。
通过这种方法能最终确保,线程池中能保留指定个数的核心线程数,关键代码为:

try {
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

某个线程在从队列获取任务时,会判断是否使用超时阻塞获取,我们可以认为非核心线程会poll(),核心线程会take(),非核心线程超过时间还没获取到任务后面就会自然消亡了。

4.1.5 线程池的核心线程数、最大线程数该如何设置

corePoolSize:核心线程数,表示线程池中的常驻线程的个数
maximumPoolSize:最大线程数,表示线程池中能开辟的最大线程个数

  • 根据服务是计算密集型还是IO密集型为基础进行设置,计算密集型应该尽量避免CPU线程上下文切换,IO密集型尽量避免线程一直阻塞等待;
  • 获取CPU核心数(可能是逻辑核心数):Runtime.getRuntime().availableProcessors()
  • CPU密集型任务:CPU核心数+1,这样既能充分利用CPU,也不至于有太多的上下文切换成本;
  • IO型任务:建议压测,或者先用公式计算出一个理论值(理论值通常都比较小)
  • 对于核心业务(访问频率高),可以把核心线程数设置为我们压测出来的结果,最大线程数可以等于核心线程数,或者大一点点,比如我们压测时可能会发现500个线程最佳,但是600个线程时也还行,此时600就可以为最大线程数
  • 对于非核心业务(访问频率不高),核心线程数可以比较小,避免操作系统去维护不必要的线程,最大线程数可以设置为我们计算或压测出来的结果。

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

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

相关文章

UE5、CesiumForUnreal实现选中区域地形压平效果

文章目录 1.实现目标2.实现过程2.1 Demo说明2.2 实现过程3.参考资料声明:本篇文章是为某位读者朋友定制开发的功能需求,所以放在了特定的专栏里,其他的朋友可以忽略这篇文章哈! 1.实现目标 基于CesiumForUnreal插件的CesiumPolygonRasterOverlay组件实现选中区域地形压平的…

WPF快速开发(1):静态计算器知识点补充

文章目录 前言WPF介绍 WPF知识点补充&#xff1a;如何开始一个简单的WPF程序新建WPF项目 页面布局Grid:货架布局DockPanel&#xff1a;停靠布局StackPanel/WrapPanel:排列布局UniformGrid&#xff1a;均分宫格布局 控件元素控件通用属性窗口元素 前言 本篇章主要介绍如何使用布…

分布式消息中间件介绍

什么是分布式消息中间件&#xff1f; 对于分布式消息中间件&#xff0c;首先要了解两个基础的概念&#xff0c;即什么是分布式系统&#xff0c;什么又是中间件。 分布式系统 “A distributed system is one in which components located at networked computers communicate an…

Maven的Web项目创建

1.创建动态Web项目 2.把项目转为Maven项目 然后就可在pom.xml中加入自己的依赖

Lua gc 机制版本迭代过程简述

文章目录 内存自动化管理的概念Lua 中的 gc 对象Lua 中内存分配和释放的底层接口Lua 5.0 gc 算法Lua 5.1 gc 算法Lua 5.2 - Lua 5.4 gc 算法参考内容 内存自动化管理的概念 内存自动化管理是指在指定内存不再被需要时可以自动被释放。通常有两种方案来实现内存的自动化管理&am…

FFmpeg时间戳

1. I帧/P帧/B帧 I帧&#xff1a;I帧(Intra-coded picture, 帧内编码帧&#xff0c;常称为关键帧)包含一幅完整的图像信息&#xff0c;属于帧内编码图像&#xff0c;不含运动矢量&#xff0c;在解码时不需要参考其他帧图像。因此在I帧图像处可以切换频道&#xff0c;而不会导致…

康托展开逆康托展开详解(原理+Java实现)

康托展开&逆康托展开详解 康托展开康托展开公式康托展开代码 逆康托展开逆康托展开具体过程尼康托展开代码逆康托的应用 使用场景 康托展开 康托展开是一个全排列到一个自然数的双射&#xff0c;常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大…

【ADS】ADS复制原理图或版图到另一个工程

直接Ctrl CCtrl V无法粘贴 可以先导入要复制的工程 加入工程&#xff0c;复制完后在勾掉工程

RuoYi-VUE : make sure to provide the “name“ option

前言 略 错误 错误原因 theme-picker 组件未被注册。 解决 src/App.vue代码恢复成若依的代码即可。&#xff08;PS&#xff1a;不知道代码被谁修改了&#xff09; 缺少这一段&#xff1a; <script> import ThemePicker from "/components/ThemePicker";…

基础IO与文件系统

全文目录 C语言的文件操作函数系统调用接口openwritereadclose Linux中一切皆文件文件描述符重定向 缓冲区为什么fflush能直接找到缓冲区进行刷新 文件系统 C语言的文件操作函数 参考文章&#xff1a; C语言文件操作【基础知识 顺序读写 文件版通讯录】C语言文件操作收尾【…

FPGA工程中eclipse软件常见的错误

错误一&#xff1a; Unresolved inclusion: "altera_avalon_uart_regs.h" Unresolved inclusion: "system.h"Description Resource Path Location Type Type alt_u8 could not be resolved hello_world.c /UART line 37 Semantic Error描述&#xff1a;这个…

【计算机网络 02】物理层基本概念 传输媒体 传输方式 编码与调制 信道极限容量 章节小结

第二章 -- 物理层 2.1 物理层基本概念2.2 物理层下的传输媒体2.3 传输方式2.4 编码与调制2.5 信道极限容量2.6 章节小结 2.1 物理层基本概念 2.2 物理层下的传输媒体 传输媒体也称为传输介质或传输媒介&#xff0c;他就是数据传输系统中在发送器和接收器之间的物理通路 传输媒…

统信UOS安装nginx及其所需部件

/usr/local 为Linux默认软件安装路径&#xff0c;类似于C:\Program Files。 因此在local路径下新建nginx文件夹安装nginx。 下载并安装nginx &#xff08;1&#xff09;进入nginx文件夹下&#xff0c;使用 wget 命令下载nginx资源包 命令&#xff1a;wget http://nginx.org/…

微服务——http客户端Feign

目录 Restemplate方式调用存在的问题 Feign的介绍 基于Feign远程调用 Feign自定义配置 修改日志方式一(基于配置文件) 修改日志方式二(基于java代码) Feign的性能优化 连接池使用方法 Feign_最佳实践分析 方式一: 方式二 实现Feign最佳实践(方式二) 两种解决方案 Re…

Kubernetes教程(三)---纯三层网络方案

来自&#xff1a;指月 https://www.lixueduan.com 原文&#xff1a;https://www.lixueduan.com/posts/kubernetes/02-cluster-network/ 由于 COPY 过来图片无法展示&#xff0c;建议跳转到原文查看 本文主要介绍了 Kubernetes 中的 Pure Layer 3 网络方案。其中的典型例子&…

msvcp120.dll丢失的解决方法,msvcp120.dll一键修复方法

最近我遇到了一个让我头疼的问题&#xff0c;那就是在使用某个软件时出现了msvcp120.dll文件缺失的错误。这个错误导致我无法正常运行该软件&#xff0c;给我的工作和生活带来了很大的困扰。 起初&#xff0c;我尝试了一些简单的解决方法&#xff0c;比如重新安装软件、重启电脑…

Appium+python自动化(十八)- - Monkey事件

操作事件简介 Monkey所执行的随机事件流中包含11大事件&#xff0c;分别是触摸事件、手势事件、二指缩放事件、轨迹事件、屏幕旋转事件、基本导航事件、主要导航事件、系统按键事件、启动Activity事件、键盘事件、其他类型事件。Monkey通过这11大事件来模拟用户的常规操作&…

机器学习 深度学习编程笔记

sigmoid函数 def sigmoid(x):return 1.0 / (1np.exp((-x)))定义最小平方和损失函数 loss torch.nn.MSELoss()线性回归编程 如果不加噪音就成了正常的线性函数了&#xff0c;所以要加噪音。 torch.normal(0, 0.01, y.shape)torch.normal(0, 0.01, y.shape)是一个用于生成服从…

Vue 复杂json数据在el-table表格中展示(el-table分割数据)

文章目录 前言问题背景实现复杂json数据在el-table表格展示el-table-column分割线el-table-column高度 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0c;但在…

智慧税务大厅业务办理vr模拟体验提升缴税效率和质量

目前的税务部门的办事大厅&#xff0c;承载着纳税人的各种税务事项的办理&#xff0c;业务量较大&#xff0c;特别是窗口工作人员&#xff0c;在税务办理的高峰期&#xff0c;经常会遇到人手不够的情况&#xff0c;如果能够将vr技术应用的税务办理的环节中&#xff0c;让使用者…