线程池
2021-04-21 16:23:39 64 举报
AI智能生成
线程池
作者其他创作
大纲/内容
手动创建线程的危害
第一点,反复创建线程系统开销比较大,每个线程创建和销毁都需要时间,如果任务比较简单,那么就有可能导致创建和销毁线程消耗的资源比线程执行任务本身消耗的资源还要大。
第二点,过多的线程会占用过多的内存等资源,还会带来过多的上下文切换,同时还会导致系统不稳定。
使用线程池的好处
第一点,线程池可以解决线程生命周期的系统开销问题,同时还可以加快响应速度
第二点,线程池可以统筹内存和 CPU 的使用,避免资源使用不当
第三点,线程池可以统一管理资源。比如线程池可以统一管理任务队列和线程,可以统一开始或结束任务
线程池的参数
corePoolSize
核心线程数
长工
maximumPoolSize
最大线程数
零时工
keepAliveTime+unit
空闲线程存活时间
workQueue
存放任务队列
LinkedBlockingQueue
FixedThreadPool 和 SingleThreadExector
由于是无界队列,所以任务队列永远都放不满,所以不会触发生成多余核心线程数的线程
ArrayBlockingQueue
有界队列,队列满时,线程数再次新增
SynchronousQueue
CachedThreadPool 线程数可无线扩容,所以不需要任务队列来存储任务
不希望任务被拒绝,那么就需要注意设置最大线程数要尽可能大一些
核心原理
put + take的方式,才能实现他原本希望实现的一个效果
如果put的时候没有人在take,此时就会将head指针指向put操作,put线程就park挂起
DelayedWorkQueue
是 ScheduledThreadPool 和 SingleThreadScheduledExecutor 最大特点就是可以延迟执行任务
按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构
threadFactory
线程工厂 用于创建新线程
handler
处理被拒绝的任务
4种拒绝策略
AbortPolicy
抛异常
DiscardPolicy
直接丢弃,不会通知你,存在一定风险
DiscardOldestPolicy
如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务
CallerRunsPolicy
相对而言它就比较完善了,让提交任务的那个线程来执行
第一点新提交的任务不会被丢弃,这样也就不会造成业务损失
第二点能限制减缓了任务提交的速度,类似降级策略,给线程池一定缓存
拒绝时机
第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝
第二种情况是线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候
线程池特点
线程池希望保持较少的线程数,并且只有在负载变得很大时才增加线程。
线程池只有在任务队列填满时才创建多于 corePoolSize 的线程,如果使用的是无界队列(例如 LinkedBlockingQueue),那么由于队列不会满,所以线程数不会超过 corePoolSize。
通过设置 corePoolSize 和 maximumPoolSize 为相同的值,就可以创建固定大小的线程池。
通过设置 maximumPoolSize 为很高的值,例如 Integer.MAX_VALUE,就可以允许线程池创建任意多的线程
常见的线程池
FixedThreadPool
固定线程数的线程池
核心线程数与最大线程数相对
缺点
如果我们对任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终大量堆积的任务会占用大量内存,并发生 OOM ,也就是OutOfMemoryError,这几乎会影响到整个程序,会造成很严重的后果。
CachedThreadPool
可以称作可缓存线程池
特点在于线程数是几乎可以无限增加的 Integer.MAX_VALUE
线程闲置时还可以对线程进行回收
它也有一个用于存储提交任务的队列
但这个队列是 SynchronousQueue,队列的容量为0,实际不存储任何任务,它只负责对任务进行中转和传递,所以效率比较高
缺点
不限制线程的创建,当任务数量特别多的时候,就可能会导致创建非常多的线程,最终超过了操作系统的上限而无法创建新线程,或者导致内存不足
ScheduledThreadPool
定时或周期性执行任务
service.schedule(new Task(), 10, TimeUnit.SECONDS)
10秒后执行任务
service.scheduleAtFixedRate(new Task(), 10, 10, TimeUnit.SECONDS);
以固定的频率执行任务
它的第二个参数 initialDelay 表示第一次延时时间,第三个参数 period 表示周期,也就是第一次延时后每次延时多长时间执行一次任务
service.scheduleWithFixedDelay(new Task(), 10, 10, TimeUnit.SECONDS)
与第二种方法类似,也是周期执行任务
不管任务需要花多久执行,以任务结束的时间为下一次循环的时间起点开始计时
缺点
它采用的任务队列是 DelayedWorkQueue,这是一个延迟队列,同时也是一个无界队列,所以和 LinkedBlockingQueue 一样,如果队列中存放过的任务,就可能导致 OOM。
SingleThreadExecutor
它会使用唯一的线程去执行任务,原理和 FixedThreadPool 是一样的,只不过这里线程只有一个
缺点
同 FixedThreadPool
SingleThreadScheduledExecutor
它实际和第三种 ScheduledThreadPool 线程池非常相似,它只是 ScheduledThreadPool 的一个特例,内部只有一个线程
缺点
它采用的任务队列是 DelayedWorkQueue,这是一个延迟队列,同时也是一个无界队列,所以和 LinkedBlockingQueue 一样,如果队列中存放过的任务,就可能导致 OOM。
ForkJoinPool
第一点它非常适合执行可以产生子任务的任务
参数
线程池参数
如何配置合适的线程数
CPU密集型任务,加解密 压缩 计算
线程数为cpu核心数的 1-2倍
线程数越多,上下文切换越多,降低性能
耗时IO型任务 数据库 文件读写
线程数可为CPU核心数的很多倍
IO读写速度比CUP速度慢,不设置倍数大一点,让费CPU的资源
总结
线程的平均工作时间所占比例越高,就需要越少的线程
线程的平均等待时间所占比例越高,就需要越多的线程;
针对不同的程序,进行对应的实际测试就可以得到最合适的选择。
线程池关闭
shutdown
安全地关闭一个线程池,不是立刻就被关闭
可能还有很多任务正在被执行 闭,因为这时线程池中可能还有很多任务正在被执行,
在执行完正在执行的任务和队列中等待的任务后才彻底关闭
调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务
isShutdown
线程池是否已经开始了关闭工作,
结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程
isTerminated
线程池是否真正“终结”了,
这不仅代表线程池已关闭 ,同时代表线程池中的所有任务都已经都执行完毕了
awaitTermination
在时间范围内,等待线程终结
shutdownNow
立刻关闭线程池,
首先 将线程池中所有发送 interrupt 中断信号 尝试中断这些任务的执行
然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试
状态
类型
-1:RUNNING
0:SHUTDOWN
不接收新任务,但是会处理队列中的任务
1:STOP
不接收新任务,也不处理队列中的任务,会打断进行中的任务
2:TIDYING
所有任务都终止了,workerCount为0
3:TERMINATED
已完成,终结状态
转换
RUNNING -> SHUTDOWN
shutdown()
(RUNNING or SHUTDOWN) -> STOP
shutdownNow()
SHUTDOWN -> TIDYING
队列和线程池都是空的
STOP -> TIDYING
线程池是空的
TIDYING -> TERMINATED
terminated()方法被触发
线程池复用的原理
源码
线程池新建线程的机制
图片
源码
线程池要点
corePoolSize
核心线程数,不会被自动回收
会已 take 阻塞式从队列中获取任务
线程池
无界队列,有界队列,同步队列
SynchronousQueue
不存储任务,只做传递作用
如果put的时候没有人在take,此时就会将head指针指向put操作,put线程就park挂起
有界队列
队列满了的时候
不超过maximumPoolSize
创建非core方式创建线程
直接创建线程,直接运行
执行完当前任务,获取下一个任务时
poll + 超时时间(keepAliveTime)的方式,等待过后,获取不到任务会销毁这个worker
若线程数已达到maximumPoolSize
使用拒绝策略
0 条评论
下一页