线程池自查注意点

news2024/9/21 14:45:28

文章目录

    • 线程池自查注意点
      • 1、线程池的标准创建方式
      • 2、线程池的任务调度流程
      • 3、避免使用Executors快捷创建线程池
        • 3.1、newSingleThreadExecutor()
        • 3.2、newCachedThreadPool()
        • 3.3、ScheduledThreadPool()
      • 4、避免在方法中创建线程池
      • 5、不要盲目使用同步队列
      • 6、使用线程池,要确保ThreadLocal不会复用

线程池自查注意点

​ 该记录首先将介绍线程池的基本概念,在介绍完之后再举例论证当前各项目中存在的线程池创建问题,用以自查。

1、线程池的标准创建方式

     // 使用标准构造器构造一个普通的线程池
     public ThreadPoolExecutor(
       int corePoolSize,            // 核心线程数,即使线程空闲(Idle),也不会回收
       int maximumPoolSize,                 // 线程数的上限
       long keepAliveTime, TimeUnit unit,   // 线程最大空闲(Idle)时长 
       BlockingQueue<Runnable> workQueue,     // 任务的排队队列
       ThreadFactory threadFactory,                         // 新线程的产生方式
       RejectedExecutionHandler handler)    // 拒绝策略

2、线程池的任务调度流程

(1)如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程。

(2)如果线程池中总的任务数量大于核心线程池数量,新接收的任务将被加入阻塞队列中,一直到阻塞队列已满。在核心线程池数量已经用完、阻塞队列没有满的场景下,线程池不会为新任务创建一个新线程。

(3)当完成一个任务的执行时,执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空,其中所有的缓存任务被取光。

(4)在核心线程池数量已经用完、阻塞队列也已经满了的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。

(5)在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出maximumPoolSize。如果线程池的线程总数超过maximumPoolSize,线程池就会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略。

调度流程图如下:

调度流程

3、避免使用Executors快捷创建线程池

​ Executors工厂提供了4种快捷创建池的方法,这里将介绍为什么这几种线程池不能在生产环境直接使用,风险是哪些。

3.1、newSingleThreadExecutor()

         public static ExecutorService newSingleThreadExecutor()
         {
             return new FinalizableDelegatedExecutorService
                     (new ThreadPoolExecutor(
                             1,             // 核心线程数
                             1,             // 最大线程数
                             0L,            // 线程最大空闲(Idle)时长
                             TimeUnit.MILLISECONDS,         //时间单位:毫秒
                             new LinkedBlockingQueue<Runnable>()      //无界队列
                     ));
         }

​ 使用该方法创建线程池所存在的问题在于使用的是无界队列,如果任务提交速度持续大于任务处理速度,就会造成队列大量阻塞。如果队列很大,很有可能导致JVM的OOM异常,甚至造成内存资源耗尽。FixedThreadPool()的潜在问题也是因此,不在赘述。

3.2、newCachedThreadPool()

         public static ExecutorService newCachedThreadPool()
         {
             return new ThreadPoolExecutor(
                     0,                             // 核心线程数
                     Integer.MAX_VALUE,             // 最大线程数
                     60L,                           // 线程最大空闲(Idle)时长
                     TimeUnit.MILLISECONDS,         // 时间单位:毫秒
                     new SynchronousQueue<Runnable>() // 任务的排队队列,无界队列
             );
         }

​ 以上代码通过调用ThreadPoolExecutor标准构造器创建一个核心线程数为0、最大线程数不设限制的线程池。所以,理论上“可缓存线程池”可以拥有无数个工作线程,即线程数量几乎无限制。“可缓存线程池”的workQueue为SynchronousQueue同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,正因为“可缓存线程池”可以无限制地创建线程,不会有任务等待,所以才使用SynchronousQueue。

​ 使用Executors创建的“可缓存线程池”的潜在问题存在于其最大线程数量不设限上。由于其maximumPoolSize的值为Integer.MAX_VALUE(非常大),可以认为可以无限创建线程,如果任务提交较多,就会造成大量的线程被启动,很有可能造成OOM异常,甚至导致CPU线程资源耗尽。

3.3、ScheduledThreadPool()

       public ScheduledThreadPoolExecutor(int corePoolSize)
         {
             super(corePoolSize,                    // 核心线程数
                     Integer.MAX_VALUE,     // 最大线程数
                     0,                // 线程最大空闲(Idle)时长
                     NANOSECONDS,//时间单位
                     new DelayedWorkQueue()  //任务的排队队列
             );
         }

​ 使用Executors创建的“可缓存线程池”的潜在问题存在于其最大线程数量不设限上。由于其线程数量不设限,如果到期任务太多,就会导致CPU的线程资源耗尽。

4、避免在方法中创建线程池

​ 一般我们可以交由spring管理构建单例的线程池bean,或者是成为类的静态成员,总之,让线程池在系统中作为单例存在提供服务即可,除非特殊需求,不考虑在方法中创建线程池。

​ 除非是那种线程池开的太多占用了太多线程资源,才需要考虑动态的线程池创建和关闭,用以释放在空闲状态下的核心线程资源给其他的线程使用。原因是线程池的核心线程在空闲状态下,默认也是不会关闭的。

5、不要盲目使用同步队列

​ SynchronousQueue是一个比较特殊的阻塞队列实现类,SynchronousQueue没有容量,每一个插入操作都要等待对应的删除操作,反之每个删除操作都要等待对应的插入操作。也就是说,如果使用SynchronousQueue,提交的任务不会被真实地保存,而是将新任务交给空闲线程执行,如果没有空闲线程,就创建线程,如果线程数都已经大于最大线程数,就执行拒绝策略。使用这种队列需要将maximumPoolSize设置得非常大,从而使得新任务不会被拒绝。

列举一个例子

	private Integer defaultExePoolSize = 30;
	private ThreadPoolExecutor executor = new ThreadPoolExecutor(defaultExePoolSize, defaultExePoolSize, 0L, TimeUnit.MILLISECONDS,
            new SynchronousQueue<Runnable>(),
            new ThreadFactoryBuilder().setNameFormat("Livelihood-gate-writeoff-call-back-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());

​ 该线程池创建了一个核心线程数和最大线程数都为30的线程池,因为核心资源已经分配的够多了,其实占用了很多的系统线程资源。一般情况下我们使用线程池是为了异步去处理一些任务,从而提升系统的性能。但是该线程使用的是同步队列,则会造成什么影响呢?

​ 只需要每秒30的并发,就可以让线程池进入拒绝状态,然而这是一处老代码了,为什么一直没有出问题是因为线程的拒绝策略选择的是调用者执行策略,即将原本应该异步处理的任务变为同步执行,虽然不会出问题,但是与使用线程池提升系统性能的初衷已经背道而驰了。

​ 往往使用同步队列的场景,其最大线程数都取得是Integer最大值,这就需要大家考虑,因为并发请求太高而创建了大量线程造成cpu计算资源耗尽的风险。

6、使用线程池,要确保ThreadLocal不会复用

在早期的JDK版本中,ThreadLocal的内部结构是一个Map,其中每一个线程实例作为Key,线程在“线程本地变量”中绑定的值为Value(本地值)。早期版本中的Map结构,其拥有者为ThreadLocal,每一个ThreadLocal实例拥有一个Map实例。

在JDK 8版本中,ThreadLocal的内部结构发生了演进,虽然还是使用了Map结构,但是Map结构的拥有者已经发生了变化,其拥有者为Thread(线程)实例,每一个Thread实例拥有一个Map实例。另外,Map结构的Key值也发生了变化:新的Key为ThreadLocal实例。

在JDK 8版本中,每一个Thread线程内部都有一个Map(ThreadLocalMap),如果给一个Thread创建多个ThreadLocal实例,然后放置本地数据,那么当前线程的ThreadLocalMap中就会有多个“Key-Value对”,其中ThreadLocal实例为Key,本地数据为Value。

这里为什么map的持有者会发生变化,是因为,如果map的持有者是ThreadLocal,那么当线程消亡时,ThreadLocal中依然存在着改条记录,每个ThreadLocal中的map大小与线程数量成正相关

因为ThreadLocalMap拥有者变成了线程,这个map的key是ThreadLocal实例,value,是存储的值,ThreadLocalMap是会随着任务的结束而消亡,但是如果在线程池中,线程复用,这就会造成任务已经结束了,但是线程没有消亡,上一个任务的ThreadLocal依然保留下来了,因此,使用ThreadLocal去保存变量时,应该保证在线程任务完成时,通过钩子函数清空其ThreadLocal

创建线程池里可以重写以下方法。

     //任务执行之前的钩子方法(前钩子)
     protected void beforeExecute(Thread t, Runnable r)   { }
     //任务执行之后的钩子方法(后钩子)
     protected void afterExecute(Runnable r, Throwable t) { }
     //线程池终止时的钩子方法(停止钩子)
     protected void terminated() { }

后钩子)
protected void afterExecute(Runnable r, Throwable t) { }
//线程池终止时的钩子方法(停止钩子)
protected void terminated() { }


还记得为啥咱们的api实现类都有一个MDC.clear()吗?因为MDC就封装了几个ThreadLocal类型的属性,比如日志id,并且Tomcat也是通过线程池去调度任务的,tomcat的线程池没有提供在任务完成时清空其threadLocal,因此我们才需要在finally块中去清空。

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

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

相关文章

MySQL库的操作

文章目录MySQL库的操作创建数据库创建数据库案例字符集和校验规则查看系统默认字符集以及校验规则查看数据库支持的字符集查看数据库支持的字符集校验规则校验规则对数据库的影响操纵数据库查看数据库显示创建语句修改数据库删除数据库备份和恢复数据库的备份和恢复表的备份和恢…

Cracking the Safes之Linux系统下gdb调试

Cracking Safe是什么 挑战是找出四个保险箱中每个保险箱预期的正确的5个输入集。在运行二进制安全程序时,您需要一次输入一个猜测,如下所示: 其实,就是输入5次,程序会对输入内容进行判断,只有符合程序要求才能成功,任务就是逆向找到正确的字符串!!! 解题思路 反汇…

mac pro M1(ARM)安装:centos8.0虚拟机

0.引言 mac发布了m1芯片&#xff0c;其强悍的性能收到很多开发者的追捧&#xff0c;但是也因为其架构的更换&#xff0c;导致很多软件或环境的安装成了问题&#xff0c;之前我们讲解了如何安装centos7。这次我们接着来看如何在mac m1环境下安装centos8 1.下载 1.1 安装VMwar…

Java基于springboot+vue的五金用品销售购物商城系统 前后端分离

五金用品是当前很多家庭和维修人员必备的工具&#xff0c;他们可以让维修变的更加简单&#xff0c;甚至有很多维修必须有配套的专业工具才能够完成&#xff0c;但是很多时候人们在五金店购买这些五金用品的时候不是价格昂贵就是缺少一些想要的工具&#xff0c;这个是通过开发一…

Guava 对 Map的操作

Guava是google公司开发的一款Java类库扩展工具包&#xff0c;内含了丰富的API&#xff0c;涵盖了集合、缓存、并发、I/O等多个方面。使用这些API一方面可以简化我们代码&#xff0c;使代码更为优雅&#xff0c;另一方面它补充了很多jdk中没有的功能&#xff0c;能让我们开发中更…

C语言刷题(2)

&#x1f412;博客名&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 文件拷贝 问题描述&#xff1a; 小蓝正在拷贝一份文件&#xff0c;他现在已经拷贝了 t 秒时间&#xff0c;已经拷贝了 c 字节&#…

解决eclipse导入svn项目报 403Forbidden

解决eclipse导入svn项目报 403Forbidden问题&#xff1b; 首先&#xff0c;产生这个问题的原因&#xff1a;①导入的svn项目没有权限&#xff1b;②上次导入的svn项目在身份验证的时候保存了用户名以及密码&#xff1b;&#xff08;我遇到这个情况的原因是因为②&#xff09; …

个人网页制作 个人网页设计作业 HTML CSS个人网页模板 大学生个人介绍网站毕业设计 DW个人主题网页模板下载 个人网页成品代码 个人网页作品下载

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

编码器的电路介绍

编码器的结构特点以及以及使用 对于8线到三线的编码器&#xff0c;一定是八线输入&#xff0c;三线输出&#xff0c;有十一条线 但是74HC148是一个16引脚的芯片 有十一线上述的信号&#xff0c;还有电源线以及地线&#xff0c;此时我们就有了13条线 另外的线则是归于控制信…

kubernetes深入理解之Service

版权声明&#xff1a;本文为CSDN博主「开着拖拉机回家」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&#xff0c;转载请附上原文出处链接及本声明。 主页地址&#xff1a;开着拖拉机回家的博客_CSDN博客-Linux,Java基础学习,MySql数据库领域博主 目录 一、概述 1.1 Serv…

【salesforce平台基础】-想到啥写点啥

【salesforce基础】-想到啥写点啥1.salesforce架构2.学习过程中常见的几个“公司”&#x1f92d;3.术语4.平台的用途&#xff08;举例说明&#xff09;5.AppExchange&#xff08;软件应用商店&#xff09;6.sandbox7.平台入门1.salesforce架构 salesforce是一家云公司&#xf…

7.关于线性回归模型的QA

为什么使用平方损失而不是绝对差值呢&#xff1f; 答&#xff1a; 二者区别不大&#xff0c;但是绝对差值是一个不可导的函数&#xff0c;在零点的时候&#xff0c;绝对差值的导数会有点难求。 损失为什么要求平均&#xff1f; 答&#xff1a;求平均的话&#xff0c;梯度是在…

原语科技宣布完成千万级天使+轮融资,致力于打造隐私计算标准化产品

原语科技 开放隐私计算 开放隐私计算 开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神&#xff0c;专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播&#xff0c;愿成为中国 “隐私计算最后一公里的服务区”。 180篇…

【基础算法】多项式三大运算 C++实现

●多项式计算 一维多项式就是包含一个变量的多项式&#xff0c;一个一维多项式示例如下&#xff1a; 一维多项式求值就是对于上述多项式&#xff0c;计算在指定的x处的函数值。一个通用的计算多项式值的算法可以采用递推的方式&#xff0c;可以将上述多项式变为如下的等价形式…

位运算 离散化 区间和算法

目录一、位运算1.1 思路1.1 例题&#xff1a;二进制中1的个数二、离散化2.1 概念2.2 例题&#xff1a;区间和三、合并区间3.1 概念3.2 例题&#xff1a;合并区间一、位运算 1.1 思路 首先知道一个概念&#xff1a;一个正整数的负数等于其按位取反后1 -x ~x 1 举个例子&…

干货——生产型企业的供应商管理系统模板

供应商管理主要是是通过提高供货产品和服务质量及交付能力&#xff0c;缩短企业采购周期和生产成本&#xff0c;从而提升产品核心竞争力。随着如今信息技术的发展&#xff0c;采用先进的信息化手段更能够提升供应商管控能力&#xff0c;实现资源的有效整合&#xff0c;从而加强…

[附源码]计算机毕业设计疫苗药品批量扫码识别追溯系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

测试服务器的udping值

测试服务器的udping值参考下载工具步骤一&#xff1a;在服务器上启动UDP Echo服务(必须)启动**UDP Echo服务**步骤二&#xff1a;在客户端下载UDPing工具步骤三&#xff1a;在客户端测试UDPing值参考 https://help.aliyun.com/document_detail/158771.html UDPing项目地址: h…

阿里资深架构师整理分享的分布式系统架构:技术栈详解与进阶文档

前言 有人调侃我们说&#xff1a; 程序员不如送外卖。送外卖是搬运食物&#xff0c;自己是搬运代码&#xff0c;都不产出新的东西……透支体力&#xff0c;又消耗健康&#xff0c;可替代性极强&#xff0c;30岁之后就要面临被优化的危险……想跳槽&#xff0c;但是更高的平台…

PyTorch 2.0 重磅发布:一行代码提速 30%

在今天的 PyTorch 2022 开发者大会上&#xff0c;PyTorch 团队发布了一个新特性torch.compile&#xff0c;这个新特性将 PyTorch 的性能推向了新高度&#xff0c;并开始将 PyTorch 的部分实现从 C 中迁移到 Python 中。他们相信这是 PyTorch 一个实质性的新方向--因此称之为 **…