008-线程池

线程池

需要异步操作的时候往往是把任务提交到线程池中
我们来详细分析下线程池在这里插入图片描述

线程数的设置

CPU密集型

CPU核数+1
+1是避免线程出现中断或等待,空闲出来的CPU可以继续执行新的线程
这样设置是避免线程切换带来的性能损耗
因为最大处理能力就是CPU的核数

IO密集型

IO等待时间越长,可以设置的线程数越多
依据是IO的等待时间
因为线程进入等待会交出CPU,CPU此时去执行其他任务即可
在这里插入图片描述
理论公式
在这里插入图片描述
这个是在“实验室环境下的理论公式”
而耗时可以从各种运维工具中获得,最简单的就是jvisualvm
在这里插入图片描述

实际最佳线程还是需要进行压测

核心线程和最大线程的设置

如果一直调用很高,那么直接设置一致,让线程池直接进入最大负载运行
如果偶尔调用很高,那么就可以少设置点核心线程,共低调用时使用,设置最佳线程作为高调用时使用。这样可以节省资源。
这里说的调用是线程池提交的任务调用

阻塞队列设置

这个可以参考上一节,选择下那个合适
但是一定要设置队列容量
避免提交任务过多而引起oom
而且如果任务提交的过多
晚提交的任务可能还没执行完或者还没有执行结束前端就已经超时了
在这里插入图片描述
我们可以看到如果设置的太大而系统处理不了的话最后提交的任务可能需要5s才能执行完
极端情况下可能线程池永远都没办法在期望的时候处理完任务
应该根据自己业务预估下承载能力
多余的任务交给拒绝策略来处理

线程池执行

创建核心线程

JDK自带的线程池构造方法new对象是没有创建线程的
只有在提交任务的时候会新增核心线程
第一步判断是否到达核心线程数
没有就创建后执行

核心线程创建够了

继续提交任务则进入等待队列
线程会在空闲后获取任务继续执行

如果有核心线程已经执行完任务了
则调用出队方法
因为是阻塞队列所以会阻塞在出队方法上
等待任务进入等待队列就会唤醒线程执行

队列满了判断最大线程数

此时如果线程数还没有到达最大线程数
则继续创建线程执行

最大线程数也够了

进入拒绝策略

是否<核心线程数
是否入队成功
是否添加成功
开始
是否
添加核心线程并执行任务
END
进入等待队列
是否
添加非核心线程并执行
是否
执行拒绝策略

线程池线程关闭

正常超时淘汰

实际情况比较复杂我们可以想一下所有任务都执行完了,并且此时线程数量大于核心线程数
现在线程池中的线程都阻塞在阻塞队列的出队方法
这个方法是有超时时间的,如果到超时时间还没有被唤醒,那就说明要进入超时淘汰了
因为线程是依靠循环来保持活跃,那么所有待淘汰线程在循环的时候对全部线程数CAS -1 成功的线程不再循环,就会自然淘汰了(如下图)
核心线程也可以开启超时淘汰allowCoreThreadTimeOut(true)

在这里插入图片描述
在这里插入图片描述

执行异常线程

原线程会推出
但是会在processWorkerExit方法中会替补一个线程(核心、非核心都会补充)

这里可能会有疑问:为什么要这么麻烦?一个丢掉在创建一个?
因为这里其实是希望把异常提交到主线程,让主线程有所感知,而不是内部消化掉,如果不希望抛出线程,需要业务方自己处理

线程池关闭

执行shutdown() 或者 shutdownNow() 则阻塞方法会抛出中断异常
在中断异常处理中会把超时标志位改为已超时,则进入超时淘汰

线程池状态

五个状态

状态解释
Running接受新的任务且会处理队列中的任务
Shutdown不能接受新的任务,会处理队列中的任务,执行完后停掉所有线程
Stop不能接受新的任务,不会处理队列中的任务,停掉所有线程
Tidying在最后一个线程停掉后CAS线程池状态为此状态,并且执行terminated(),执行完后改为Terminated
Terminated线程池为0且执行完terminated()

状态流转:
在这里插入图片描述

ctl

线程池的状态,原子int类型
此变量32位,前3位表示状态,后29位表示池内线程个数

状态比特位
Running111
Shutdown000
Stop001
Tidying010
Terminated011

源码分析

添加任务

在这里插入图片描述
如果workerCount < 核心线程数

  • 则addWorker(此任务,true),如果成功直接返回(跳出整个方法),失败则重新获取ctl并继续

如果状态为running且入队成功(offer返回true)

  • 则重新获取ctl
    判断如果不是running装填并且移除此次提交的任务成功,进入拒绝
    或者判断workerCount==0, 则addWorker(null, false)

如果状态非running或者入队失败(满了),则判断addWorker(此任务,false)是否失败
失败则进入拒绝

这个源码就验证了提交任务处理顺序为

  1. 核心线程、不关心是否空闲
  2. 入队
  3. 最大线程
  4. 拒绝

addWorker

addWorker(此任务,true)

设置跳出位:retry:
循环
在这里插入图片描述
如果状态为SHUTDOWN或者之后
且 !(状态为SHUTDOWN 且 此任务==null 且 阻塞队列不为空)

这里因为此任务不为null则只有有1种情况
池需要关闭了 就返回false了

嵌套循环
获取工作线程
如果大于最大任务数 或者 大于核心线程数
则整体返回false
否则CAS +1 工作线程数
成功则跳至跳出位retry
失败则读取状态位是否变更
变更了就跳至跳出位retry 没有就重新从跳出位retry开始循环

真实创建线程

在这里插入图片描述

红框1:创建线程

在这里插入图片描述

红框2:判断是否线程是否可以运行任务

如果线程池是running
或者线程池是shutdown firstTask是空的
:这里场景是虽然shutdown了但是队列里还有任务所以需要新增
加入workers
更新最大PoolSize
设置added标志为true

红框3:执行线程run方法

run()
在这里插入图片描述

  1. 循环获取任务 如果不为空就执行task
  2. 执行前扩展
  3. 执行后扩展
  4. 执行任务
获取任务

在这里插入图片描述
1和3配合如果工作线程比最大线程还多了那就需要-1
CAS -1 成功了就返回null 导致线程跳出while循环 自然-1
失败了就continue重新判断

2就是检查是否超时
如果需要超时则使用带时间的出队,超时了就返回null 线程自然-1
如果不需要超时则使用不带时间的出队,等着任务提交唤醒

线程池中断

在调用shutdown或者shutdownNow方法
会直接调用所有线程的阻断方法,设置中断信号
如果线程在执行任务,则执行完后重新获取任务的时候直接返回null跳出
如果线程在阻塞,直接排除异常,走上边的timeout流程跳出

shutdown或者shutdownNow方法有什么区别

shutdownNow是所有线程都会中断,有返回队列剩余的任务
shutdown是中断空闲线程,没有返回值

怎么判断空闲呢?
worker自身就是AQS实现,自己就是一把锁
在执行任务前,自己加锁
执行任务后,自己解锁
那再判断的时候可以尝试加锁,如果加锁成功就是空闲线程

AQS的state什么用

state再worker中有三个状态
-1:初始化(new Worker() 但是还没有开始准备拿任务执行)
1:加锁(lock():CAS 从0 到 1)
0:解锁(unlock():setState(0))
在这里插入图片描述

这里我们有疑问:加锁是从0到1 那怎么从初始化到加锁成功呢
线程池的线程在执行run() 的时候先调用一次unlock() 再加锁
这样就能控制线程的各个状态
在这里插入图片描述
-1就是刚初始化还没有调用run()方法的线程
1就是正在执行任务的线程
0就是空闲线程

mainLock是什么用

我们在之前知道了worker会存放到 workers中
workers是一个HashSet 不能保证线程安全
所有就借助mainLock来加锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当 http-nio-7001-exec- 线程池的数量突然增高时,可能是由以下原因导致的: 1. 突发流量:如果有大量用户同时访问网站或请求需要处理大量数据,可能会导致线程池数量突然增加。 2. 长时间运行的请求:如果请求需要很长时间才能处理完毕,线程池可能会一直保持高水平。这可能是由于处理请求的代码有性能问题。 3. 竞态条件:当不同的线程需要共享数据时,可能会出现竞争条件。这可能会导致某些线程等待其他线程完成工作,从而导致线程池数量增加。 4. 内存泄漏:如果应用程序中存在内存泄漏,可能会导致线程池数量增加。这是因为线程需要占用内存,而内存泄漏则会导致内存无法释放,从而导致线程不断增加。 要解决此问题,可以通过以下方式: 1. 检查代码逻辑,查找潜在的性能问题,例如避免使用锁、减少数据访问等。 2. 应该检查应用程序中的垃圾回收机制和内存分配,以确保内存使用状况正常。 3. 可以增加线程池的大小,以便处理更多的请求。虽然这并不能解决问题的根源,但如果您确定出现了时间敏感的性能问题,则可以使用此方法暂时缓解问题。 4. 内存泄漏是一个严重的问题,应该彻底检查应用程序和底层框架,以确保它们正常运行。 总之,应该确保线程池不会过度使用资源,且需要密切监视线程池并进行必要的调整以支持流量变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值