实战经验:如何根据系统的业务场景需求定制自己的线程池?

news/2024/4/18 14:18:30/文章来源:https://blog.csdn.net/weixin_44302240/article/details/127635841

线程池有那么多的参数和类型,在实际的开发中,我们应该如何设置呢?是直接使用Executors提供的线程池实现,还是自定义线程池?这都是我们本篇文章要回答的问题,那么就请跟随笔者一起分析一下在实战中如何根据系统的业务场景需求来定制自己的线程池吧。

目录

指定线程数量

选择合适的工作队列

自定义线程工厂

选择合适的拒绝策略

自定义线程池代码案例


一般情况下,其实Executors提供的几种实现已经足够我们使用了,比如:newCachedThreadPool()、newFixedThreadPool()以及newSingleThreadExecutor()。

如图1流程图所示,我将不同的业务场景适合的线程池类型画了出来。

图1

如果在业务场景中使用一个线程就足够了,那么直接选择拥有一个核心工作线程的newSingleThreadExecutor()就能满足要求;

如果一个线程不够,但是能够判断线程数量是有限的,那么只需要指定工作线程数量N,通过newFixedThreadPool(N)就能够满足要求;

如果需要通过创建线程来应对一定的突发流量,保证任务处理的即时性,那么使用newCachedThreadPool()也是比较合理的。需要注意的是,如果突发流量很大,比如每秒上万的突增流量,那么使用newCachedThreadPool()就需要慎重,因为很可能会导致出现java.lang.OutOfMemoryError异常。

需要注意的是,我们这里提到的newSingleThreadExecutor()以及newFixedThreadPool(N)线程池,使用的都是LinkedBlockingQueue无界队列。如果业务场景不适合使用无界队列,比如:任务携带的数据过多,且任务并发量大,那么使用基于LinkedBlockingQueue无界队列的线程池就需要慎重。

也就是说,Executors提供的默认线程池也是特定场景下才适用,并不是万能药。

那么问题来了,生产中应当如何使用线程池才比较合理呢?答案很简单,就是自定义线程池。

自定义线程池,其实就是根据自己业务场景,使用不同的参数去对线程池进行定制,从而满足具体的业务场景。

定制线程池的要点,其实就是ThreadPoolExecutor的核心构造参数的指定,主要在于指定合理的核心线程数、最大线程数、选择合适的工作队列、自定义线程工厂以及选择合适的拒绝策略。我们针对每个参数具体讨论一下。

指定线程数量

线程数量并没有一个标准答案,它主要依赖机器的CPU个数以及JVM虚拟机堆的大小,一般情况下,CPU个数是更加主要的影响因素。

实际生产中,我们根据任务关注点的不同,将任务划分为:CPU密集型(或者叫计算密集型)、I/O密集型两大类,如图2所示。也有一种叫做混合类型的任务,也就是既包含计算又包含I/O操作,这种我们不必关注。

图2

一般来说,对于CPU密集型的任务,由于CPU计算速度很快,任务在短时间内就能够通过CPU超强的计算能力执行完成,因此我们可以设置核心线程数corePoolSize为N(CPU个数)+1,之所以要设置为CPU个数加1,主要原因在于为了防止某些情况下出现等待情况导致没有线程可用,比如说发生了缺页中断时,就会出现等待的情况。因此设置一个额外的线程,可以保证继续使用CPU时间片。

而对于I/O密集型的任务,可以为最大线程数多设置一些线程。原因在于相比CPU密集型任务,I/O密集型任务在执行过程中由于等待I/O结果花费的时间要明显大于CPU计算所花费的时间,而且处于I/O等待状态的线程并不会消耗CPU资源,因此可以多设置一些线程。一般情况下,我们将其设置为CPU个数的倍数,常见的玩儿法是设置为N(CPU个数)*2。

对于I/O密集型任务,还要注意核心线程数不用设置的很大,原因在于I/O操作本身会导致上下文切换的发生,尤其是阻塞式I/O。因此建议将I/O密集型的核心线程数corePoolSize限制为1,最大线程数maximumPoolSize设置为N(CPU个数)*2。当线程池中只要一个线程的时候,能够从容应对提交的任务,此时的上下文切换相当少。然后随着任务逐渐增加,再慢慢的增加线程数量至最大线程数。这样做既不浪费资源,还很灵活的支持了任务增加的场景。

需注意的是这里给出的是一种理论的参考配置,实际开发中,由于对性能的要求以及机器配置的不同,我们不能照搬文中的配置,还需要根据具体的情况进行调整,比如考虑CPU的利用率,任务执行过程中的等待时间等。但是一般来说,使用我们提到的配置是一种比较稳妥合适的方式。关于如何计算合理的线程池大小其实是有一个公式的,这里贴一下公式的内容,公式背后的深层次的原理就留给大家去探索学习,公式内容如图3所示。

图3

在Java中,通过Runtime.getRuntime().availableProcessors()就可以很方便的获取到JVM所在机器的CPU个数,从而方便我们指定具体的线程个数。

选择合适的工作队列

我们接着来看一下如何选择合适的工作队列。

工作队列通常有无界队列、有界队列、同步队列三种类型。每个队列都有它自己的特点和用途,按照惯例还是用一张图来说明,如图4所示。

图4

在图4中,我们将每种工作队列的特点,代表的实现,以及使用的注意点都做了详细说明,大家可以认真阅读图中的内容,我们就不再赘述了。

自定义线程工厂

一般来说,我们还需要定义线程工厂,给自定义的线程池起一个个性化的名字,这有助于我们在查找日志的时候精确的定位到具体的某个线程池。

自定义线程工厂,需要实现ThreadFactory接口,此处提供一个参考实现,如图5所示,大家可以根据这个代码样例进行扩展,实现自己的线程工厂,当然也建议大家多去阅读优秀的开源框架,比如Netty、Tomcat,它们都提供了优秀的自定义的线程工厂的实现。

选择合适的拒绝策略

我们接着聊聊如何选择合适的拒绝策略,关于ThreadPoolExecutor默认的拒绝策略及其特点,我们可以参考图6。

图6

一般来说,直接使用AbortPolicy抛出异常即可,但是如果说我们要求即便触发了拒绝策略,任务也得执行完成不能丢弃,那么选择CallerRunsPolicy拒绝策略即可。如果说这几种拒绝策略都满足不了我们的需求的话,就可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可实现自定义的拒绝策略。

自定义线程池代码案例

上文中,我们对自定义线程池中需要注意的要点都进行了详细的图文并茂的讲解,相信你已经有所收获。这个部分我们就趁热打铁,编写一个完整的自定义线程池的案例。

我们以I/O密集型任务为例,实现一个自定义线程池的案例,具体代码如图7所示。

图7

我们定义了一个订单同步线程池,指定核心线程数为CPU数量+1,最大线程数为CPU数量*2,并指定非核心线程数的存活时间为60s。

我们使用了有界的LinkedBlockingQueue作为工作队列,指定大小为500,这个参数可以根据实际情况自定义,比如通过配置文件动态指定。

同时还定义了自定义的线程工厂,为线程池设置了名称“sync-order-info-thread-pool”,便于方便查询日志。最后指定了拒绝策略为CallerRunsPolicy(),保证只要JVM进程正常运行,任务一定能够被执行到。

我们只需要编写一个方法,将该自定义的线程池的引用返回,就可以让业务逻辑在需要的场景随时使用该自定义线程池了。实际开发中,我们更多的会用到Spring框架进行代码编写,我们只需要定义一个ThreadPoolExecutor的bean,即可在需要使用的地方进行注入,进而使用其进行异步任务提交等操作了,如图8所示。

图8

本节,我们重点讨论了如何根据系统的实际业务是需求自定义线程池,在接下来的文章中,我们将通过线程池来实现互联网场景下的验证码保护服务,敬请期待。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp开发微信小程序-用户授权登录和获取手机号码

小程序开放文档 uniapp开发的小程序配置,找到manifest.json,填入正确的小程序appId; hbuilderx>运行>运行到小程序模拟器(安装开发者工具),编译完成之后会直接在微信开发者工具内打开; 登录流程解析&#xff1…

【SpringBoot】一文了解SpringBoot热部署

文章目录前言手动启动热部署热部署种类手动进行热部署自动启动热部署热部署范围配置热部署的关闭总结🌕博客x主页:己不由心王道长🌕! 🌎文章说明:一文彻底搞懂SpringBoot热部署🌎 ✅系列专栏:Sp…

程序人生:去了字节跳动,才知道年薪40W的测试有这么多?

今年大环境不好,内卷的厉害,薪资待遇好的工作机会更是难得。最近脉脉职言区有一条讨论火了: 哪家互联网公司薪资最‘厉害’? 下面的评论多为字节跳动,还炸出了很多年薪40W的测试工程师 我只想问一句,现在的…

【C#】async和await

大概理解 查了一个小时的资料:async和await 发现这个大神的解释一针见血,深得我心!以最简单的例子,解释了async和await。妙~~~ 大多情况下,分开才能体现async和await的价值! 但,await 并没有…

C#中的弃元

从C#7.0开始,推出了一种新的特性:弃元,这种思想可能来源于Golang。弃元,就是不想要了的元素变量,用单下划线(_)表示,弃元在编译时起作用,就是搞编译器:这个变量我不要,你可以优化处理。我们经常在下面几个过程中使用弃元:1、元组解构赋值在使用元组解构赋值时,我们…

Linux——进程间通信——管道(文件)通信

目录 前言 一、有名管道 1、用法 2、管道分类 3、有名管道的创建 4、思考:如何进程a要将键盘获取的数据传递给另一个进程b? 5、有名管道实现进程间通信 二、无名管道 1、无名管道的创建 2、管道操作分为以下步骤 3、无名管道实现进程间通信 前言…

string类详解

文章目录1:构造string类1.1:方法1.2:测试2:size和length2.1:用途2.2:测试3:capacity3.1:用途3.2:测试4:clear4.1:用途4.2:测试5:empty5.1:用途5.2:测试6:reserve6.1:用途6.2:测试7:resize7.1:用途7.2:测试8:string的三种遍历8.1:方法一 for循环和[]重载8.2:方法二 迭代器8.2.1:…

基于CNTK/C#实现逻辑回归【附源码】

文章目录前言一、VS2022CNTK环境搭建二、逻辑回归代码构建1.逻辑回归构建2.训练数据的生成3.模型训练三、效果展示前言 本文基于CNTK实现逻辑回归二分类,并以之前的不同,本次使用C#实现,不适用python,python版的CNTK比较简单&…

Java多线程-ThreadPool线程池(三)

开完一趟车完整的过程是启动、行驶和停车,但老司机都知道,真正费油的不是行驶,而是长时间的怠速、频繁地踩刹车等动作。因为在速度切换的过程中,发送机要多做一些工作,当然就要多费一些油。 而一个Java线程完整的生命周期就包括:1、T1:创建(启动) 2、T2:运行(行驶)…

苹果IOS应用上架AppStore的流程与教程

快打包生成的苹果APP上架到苹果官方appstore商店的详细流程与教程第一步:创建app发布证书以及配置文件1、打开苹果开发者中心网站:https://developer.apple.com,点击右上角 Account 使用开发者账号登录,如下图所示:​编辑切换为居中添加图片注释,不超过 140 字(可选)2、…

基于IoT全链路实时质量-魔洛哥

简介: 通过基于IoT的全链路实时质量,业务使用狄仁杰进行全链路埋点后,可一键接入魔洛哥平台,实现终端问题的实时感知和链路分析,以及智能终端系统业务场景的全链路实时质量。整体方案接入成本低(分钟级别接入),可实现全链路的实时质量分析,以及精准的终端预警能力。帮…

JavaScript 51 JavaScript 严格模式

JavaScript 文章目录JavaScript51 JavaScript 严格模式51.1 "use strict" 指令51.2 声明严格模式51.3 "use strict"; 语法51.4 为什么使用严格模式?51.5 严格模式中不允许的事项51.6 对未来的保障51.7 警告51 JavaScript 严格模式 “use stric…

IDEA下载与安装,保姆级教程

这里写自定义目录标题1.搜索idea2.选择官方网站3.官网进入下载页面4.版本选择问题5. Ultimate和Community对比6.下载7.安装1.搜索idea 2.选择官方网站 以前idea的官网后面有官网俩字,现在没有了,你可以看他的具体网址,因为idea是Jetbrains公…

猿创征文|计算机学生必须掌握的学习工具

🍓个人主页:bit.. 🍒系列专栏:Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 目录 一.c/c使用的软件 二.GitHub和gitee的使用 三.学会如何去调试代码 修改bug 四.学习Linux上面的基本操作 五.java使用的软件 六.p…

【案例源码公开】国产AD+全志T3开发案例,为能源电力行业排忧解难!8/16通道

前 言 本文主要介绍基于全志科技T3(ARM Cortex-A7)国产处理器的8/16通道AD采集开发案例,使用核芯互联CL1606/CL1616国产AD芯片,亦适用于ADI AD7606/AD7616。CL1606/CL1616与AD7606/AD7616软硬件兼容。 备注: (1)创龙科技TL7606I模块使用AD芯片为核芯互联CL1606或ADI AD…

Softing连接解决方案——将FANUC数控机床数据集成到西门子工业边缘

2022年10月10日(哈尔),Softing发布了edgePlug FANUC CNC,其丰富了edgePlug产品系列。该产品系列基于Linux的Docker容器应用并为西门子工业边缘应用提供了控制器数据。 (Softing的edgePlug Docker容器产品为西门子工业边…

《Python+Kivy(App开发)从入门到实践》自学笔记:简单UX部件——Label标签

章节知识点总揽 4.2 Label标签 在Kivy中,Label小部件用于呈现文本,它仅支持ASCII和Unicode编码的字符串(不支持中文),在Label中,可以设置文本内容、字体、大小、颜色、对齐方式、换行、引用以及标记文字等…

【PCBA方案设计】快速体温计方案

一、电子体温计方案介绍 电子体温计由温度传感器,液晶显示器,纽扣电池,专用集成电路及其他电子元器件组成。能快速准确地测量人体体温,与传统的水银玻璃体温计相比,具有读数方便,测量时间短,测量…

DM数据库安装、登录和创建用户

DM数据库安装、登录和创建用户子安拉取镜像 wget -O dm8_docker.tar -c https://download.dameng.com/eco/dm8/dm8_20220822_rev166351_x86_rh6_64_ctm.tar docker load -i dm8_docker.tar docker images编写docker-compose.yml version: 3 services:dm8:image: dm8_single:v8.…

操作系统(九)进程通信

文章目录1 IPC1.1通信操作1.2通信链路的实现1.3通信分类1.3.1直接通信与间接通信直接通信间接通信1.3.2消息传递的特征1.4缓冲问题1.4.1容量问题2信号(signal)2.1原理2.2接收信号后处理方式2.3不足2.4实现3管道3.14消息队列5共享内存6socket机制1 IPC i…