线程池的创建及参数设置详解

一. 常见线程池

线程池的创建方法主要有两类,第一是通过Executors 创建线程池,第二是通过 ThreadPoolExecutor 创建线程池。

首先我们来看通过Executors 创建的线程池是什么样的

        1. Executors.newFixedThreadPool:创建⼀个固定大小的线程池,特点是核心线程数等于最大线程数,可控制并发的线程数,超出的线程会在有界队列中等待;
        2. Executors.newCachedThreadPool:创建一个可缓存的线程池,特点是核心线程数为0,队列采用了SynchronousQueue,没有线程来take,任务不能put进去,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
        3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
        4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
        5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执行延迟任务的线程池;
        6. Executors.newWorkStealingPool:创建⼀个抢占式执行的线程池(任务执行顺序不确定)【JDK1.8 添加】。

一般不推荐使用Executors 的方式去创建线程池,而是通过ThreadPoolExecutor 根据项目实际去设置参数创建线程池,下面我们主要分析一下线程池核心参数的设置。

二. 线程池参数设置详解

在这个问题展开前,我们先来了解一下压测,即压力测试,是确立系统稳定性的一种测试方法,通常在系统正常运作范围之外进行,以考察其功能极限和和可能存在的隐患。

压测的目的就是通过模拟真实用户的行为,测算出机器的性能(单台机器的 QPS、TPS),从而推算出系统在承受指定用户数(100 W)时,需要多少机器能支撑得住。因此在进行压测时一定要事先设定压测目标值,这个值不能太小,也不能太大,按照目前业务预估的增长量来做一个合理的评估。

压测的一些名词解释:

首先,我们线程池的参数设置最后必须要通过压测来确定,需要符合我们的业务需求。那么我们如何开始进行压测参数配置的,也就是说压测前我们线程池的参数应该如何选取,这个问题需要分情况去讨论:

情况一:当前的线程池可以占用我们服务器的所有资源

基于先辈程序员的理论基础和实践,进行线程池的参数合理调整,然后经过压测,最终确定我们的参数。

例如:

  1. 计算密集型:CPU核数 + 1
  2. IO密集型:2 * CPU核数  or  CPU利用率 * (1 + 等待时间 / 计算时间)* CPU核心数

情况二:当前线程池不可以使用机器的所有资源

由于我们目前机器不是单机部署的,分布式部署的情况,一个服务多个接口的情况,比如两个重要接口,三个可降级的接口,在这个场景下比如说一个重要接口里面需要创建一个线程池来提高单接口的处理速度,那么这个时候怎么去设置参数。也就是说,我这个线程池不能获得服务器的所有资源。

具体案例:需要拿到该服务各个接口的访问比例,比如4个接口,各个比例为 2 : 3 :1 : 1

两个核心服务接口(承载60%+)

A. 需要创建线程池,提高单接口处理速度,这时候怎么设置线程池参数(最核心的三个参数:最大线程数,核心线程数以及阻塞队列的选择)

  1. 第一个接口访问量占用 1  / 4:因此分配1/4的资源
  2. 第二个接口访问比例 3 / 8:因此分配 3/8 的资源
  3. 接下来确定最大线程数(贴合实际):不能随意的将一个线程池线程数加大,这样的话会导致其他接口不能雨露均沾,具体情况考虑CPU核数,所有接口的最大QPS,和本机活跃的live的线程数。拿到这些数据后再通过上述访问比例去分配资源。
  4. 核心线程数:初次压测设置成为一样大,测试最大并发访问的情况。撑得住,调小核心线程数。撑不住,我们还有一个阻塞队列去调整,接下来去确定阻塞队列
  5. 不推荐无界队列:使用无界队列相当于干掉了最大线程数这个参数,违背了线程池的设计初衷,还有无限积压请求,最终导致OOM问题。推荐使用有界队列:不推荐直接使用Integer.max_value,而是模拟我们访问量最大的场景来确定,将高峰访问时段与我们压测进行匹配具体参数设置:(这个时段的访问数量  减去  这个时段接口能处理的请求数量)* 150%,也就是用我们接口能处理请求后剩余的值适当提升50%作为我们有界队列的上限。
  6. 设置完后进行压测

B. 案例情况:如果按照上述条件调整了,还没达到预期怎么办?

  1. 已经达到最大线程数3/8了,那就调大队列的最大等待数(首先调整队列而不是线程数)。好处:不影响其他接口当前线程只是等待,而不是不可用
  2. 如果,业务方不同意上述等待方案:优化当前接口代码,把并发性调整一下,调高。
  3. 线程数适当再调大一点,但是不能再多很多了,否则会影响其他接口
  4. 添加机器

案例情况:如果线程核数设置不合理,会产生什么问题?

  1. 接口线程池设置太小,接口异常请求数量增多:如果使用抛异常降级策略,将会导致有大量的异常日志。会造成接口熔断或者降级(非正常性质的),导致生产事故。
  2. 接口线程池设置太大:当前接口活得挺好,但是会挤兑其他接口的资源,其他接口没有足够的资源,导致系统接口积压,系统崩溃。

总结:

  1. 先按照接口请求比例设置占用最大线程数
  2. 按照压测情况,适当设置核心线程数
  3. 按照最大情况,留出一定的等待队列阈值
  4. 饱和策略:按照具体情况具体分析
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
这只是最简单的使用,后续如果热度高的话,再录个视频教程详解吧,学到的好评给一个~ 前  言 · 之前刚接触鱼刺的时候发了个 【鱼刺线程池,自动换IP,队列重试框架(https://bbs.125.la/forum.php?mod=viewthreadtid=14178530 )】 发现热度还不错,大家还是挺喜欢多线程的操作。 常言道:授人以鱼不如授人以渔,鱼刺类模块一直感觉确实稳定好用,对于新手来说一些命令还是比较难理解的。但不知道为什么一直没有详细教程。 今天趁这次开源大赛曾个热度 讲一下鱼刺多线程应用中 线程池Ex的使用方法,废话不多说,直接开始吧。 注: 所讲内容都是自己使用中所得出来的经验,如果讲的不好或者不对得地方请大佬指导哈。 已经请示过作者了: @Bints 首先我们需要下载并载入 教程以 鱼刺类_多线程应用5.43为主:鱼刺类_多线程应用v5.4.zip (112.11 KB, 下载次数: 228) 我们先来看看“鱼刺类_线程池Ex”的命令: 几个参数先说一下: 1.初始栈大小 :可以理解为申请内存的空间的初始大小(个人是这么理解的)。必须是4KB的倍数且最小8KB(8192字节)所以最小为8*1024,不用担心太少,任何情况下Windows会根据需要动态延长堆栈的大小 2.是否在UI线程 :如果填写了真,那么在循环里面会加个"处理事件()"的命令来让消息循环 去队列处理窗口组件操作 防止执行的时候窗口卡死,(记得在哪里看到过线程中处理事件()是没有效果的。不太懂~~) 1. 置_初始栈大小()  设置初始栈的大小,也可以在创建()的第五个参数设置。此命令可以在线程池工作中设置。 2. 置_空闲回收事件()  设置线程空闲回收的时间,也可以在创建()的第三个参数设置,此命令可以在线程池工作中设置。 3. 置_最大线程数()  设置最大线程数量,也可以在创建()的第二个参数设置,此命令可以在线程池工作中设置。 4. 创建() :顾名思义 创建线程池。 5. 投递任务() ,向线程池中投递一个可执行的函数子程序指针,和投递任务_int()基本一模一样,在内部自动转换成指针地址到整数(子程序指针) 6. 投递任务_int()  向线程池中投递一个可执行的函数指针地址 7. 等待任务动态()  :就是等待线程,到指定的等待时间才会往下执行,可以用 触发任务动态来取消等待。 8. 触发任务动态() .这个需要和等待任务动态一起用,也可以理解为 放弃等待中的任务 继续向下执行 9. 暂停()  暂停正在工作的线程,配合 事件_暂停() 使用效果最佳,后续会详解。 10. 事件_暂停()   需要配合暂停命令。如果系统发出了暂停命令返回假 如果正常工作返回真,如果正在销毁的话也会返回假。 11. 继续()  取消暂停。 12. 取_队列任务数()  获取队列中的正在执行的线程数量。 13. 取_空闲任务数()  获取队列中的空闲线程数量,严格控制线程数量可以用到此命令,后续会详解。 14. 取_是否空闲()  获取线程池状态是否彻底空闲,也就是说任务是否全部执行完毕,可以作为后续投递任务完任务后的判断。 15. 取_线程池容量()  获取线程池的可执行的最小线程数量 16. 取_最大线程容量()  获取线程池中可执行的最大线程数量 17. 取_执行线程数()  获取正在工作的线程数量 18. 取_状态()  获取线程正在工作的状态,返回值为常量中的: #线程池_未启动 #线程池_正在工作,#线程池_正在销毁,#线程池_正在创建 下面开始实战,将会用到所有线程池Ex中的命令 首先载入模块后在程序集变量中创建一个线程池Ex。 创建一个按钮。在按钮事件中写入:要执行的任务数量为1000 线程数量为50 如果已知 执行数量为1000了 直接计次循环 写下去可能执行不够准确,因为不排除会投递失败的情况。所以我们: 如下图:只有在投递任务成功的时候 计次才会递增。 但是每次循环都会判断 递增的计次是否小于任务数量, 如果小于就继续执行,如果大于就说明投递的任务数量已经达到了目标任务数,就可以跳出循环了 上图中:投递任务()命令 传递了两个参数 一个是局_计次 一个是 0, 投递 局_计次 可以在任务函数中获取到 用处也比较大,比如可以用作超级列表框的索引。(前提是已经插入了) 等待任务动态().为什么要等待呢,又是时候你投递的是内存指针,投递进去后等待 任务函数将它获取到并释放完毕后触发任务动态就好了 比如: 这样看着没什么问题 是吧~~ 内存方面的知识后续再说把 先掠过,只是这样演示这节只讲线程池Ex 但是如果我们模拟一下真是线程场景 加个延时() 如上图所示,如果有延时的话线程池投递完任务直接销毁 会导致任务被中断,或者放弃了

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李孛欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值