- 参考文献:
Policy:策略 Synchronous:同步的 Transfer:迁移、转移、转让
terminated:终止
block:阻塞 blocking:阻塞
Scheduled:预先安排的、预订
1. 为什么使用线程池
1.1 池化思想
- 线程池可以认为是更普遍的 “池化资源” 技术的一个经典示例
- 其产生的原因是,由于创建 / 销毁对象需要对内存资源或者其他相关资源进行相对操作
- 所以某些消耗资源的对象的创建和销毁是比较费事的(java 虚拟机还需要管理每一个对象的生命周期)
所以资源优化的一个方向就是减少创建和销毁对象的次数,尽可能地复用。比如说数据库连接池…
1.2 答案
属于面试题,还有一些变相的提问
- 使用线程池的好处有哪些?
- 线程池有什么优势?
- ……..
标准的答案
参数名称:
corePoolSize
- 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态
它们也不会被销毁,除非设置了
allowCoreThreadTimeOut = true
,则核心线程会随着设定的存活时间截止而销毁2.2 线程池最大线程数
参数名称:
maximunPoolSize
- 任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行
- 如果没有则会缓存到工作队列中,如果工作队列满了,才会创建一个新线程
- 然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部
线程池不会无限制的去创建新线程,有一个最大线程数量的限制,这个数量即由
maximunPoolSize
指定2.3 线程存活时间
线程名称:
keep Alive Time
- 一个线程如果处于空闲状态,并且当前的线程数量大于
corePoolSize
设置的数量 - 那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由
keepAliveTime
来设定 如果核心线程设置
allowCoreThreadTimeOut
,也代表核心线程的存活时间2.4 存活时间单位
-
2.5 工作队列
2.5.1 BlockingQueue详解
BlockingQueue是什么?BlockQueue也叫阻塞队列,是java并发包里面非常常用的一个接口
- 阻塞队列有什么特点?当队列为空的时候,获取对象会阻塞;当队列满的时候放入对象会阻塞
- BlockQueue的作用?实现了队列该有的基本功能,多线程环境下自动的管理线程的等待和唤醒
BlockQueue的核心api,具体查看项目
java-base
2.5.2 work queue的特性
参数名称:
work queue
- 有界概念:有界代表队列的容量是有上限的,无界则是相反(有界的数组可以防止资源耗尽问题)
- 存储等待执行的任务,新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务
jdk
中提供了六种工作队列(见下面详解) | 类名 | 有界 | 特性 | | —- | —- | —- | | ArrayBlockingQueue | 有 |
1. 内部基于数组实现的,按FIFO排序,初始化的时必须指定容量大小,并且一单指定就无法修改了
1. 初始化:新任务进来后,放进队列,然后则会有一个线程执行该任务
1. 当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,创建一个新的线程来调用
1. 如果队列未满的,线程数量已经达到maxPoolSize,新的任务则会发在队列尾部等待执行
1. 如果队列已经是满的,线程数量已经达到maxPoolSize,则会执行拒绝策略
| | LinkedBlockingQueue | 有/无 |
1. 底层基于链表实现的,按照FIFO排序
1. 可有界可无界:初始化指定容量就是有界;初始化不指定容量默认设置为Interger.MAX就是无界(原因:因为正常情况下如果你的容量达到Interger.MAX,代表你的jvm已经崩掉了,所以无界)
1. 当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的
| | ProorityBlockingQueue | 无 |
1. 带有优先级的阻塞队列,优先级通过参数Comparator实现
1. 允许插入null对象
1. 元素必须实现Comparable接口,队列的排序规则要使用
| | SynchronousQueue | 有 |
1. 不存储元素,内部容量为0
1. 一个线程发起插入操作后,就会被阻塞,直到另外一个线程发起相应的删除操作才会恢复,因此又称为同步队列
1. 主要实现了tack()和put()操作,且需要配对使用
1. 生产者放入一个任务必须等到消费者取出这个任务,也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略
| | DelayQueue | 无 |
1. 其中的元素必须实现delayed接口
1. 其中的元素必须实现排序,一般按过期时间优先排序
1. 使用场景:定时关闭连接、缓存对象、超时处理场景
| | LinkedTransferQueue | 无 |
1. 底层基于链表
1. 用于数据交换
1. 比其他队列多了transfer()和trytransfer()方法
|
2.6 线程工厂
- 参数名称:
thread Factory
创建一个新线程时使用的工厂,可以用来设定线程名、是否为守护线程
(daemon)
线程……参数名称:
handler
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4种拒绝策略
-
4. 线程池的状态
running:可以接收新的任务,也可以处理队列中的任务
- shutdown:不能接收新的任务,但是可以处理阻塞队列中的任务
- stop:线程池停止
- tidying:中间状态,暂时无意义
- terminated:终止状态
5. Executors.class
executors类提供了,一系列的常见的线程池类型,拿出来直接使用 下面的题目定义是根据返回的类型来定义的,前两种都还是ThreadPool大类
5.1 ThreadPoolExecutor
各个类之间关系 ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor
5.1.1 SingleThreadExecutor
ExecutorService singleThreadExecutor = Executors._newSingleThreadExecutor_();
- 创建一个单线程的线程池,也就是说任何时间线程池中只有一个线程,保证任务按照指定顺序执行
- 如果该线程因为异常而结束,那么会有一个新的线程来替代它
-
5.1.2 FixedThreadPool
ExecutorService fixedThreadPool = Executors._newFixedThreadPool_(1);
- 创建固定大小的线程池,任意时间线程池中的线程数目是固定的
- 每次提交一个任务就创建一个线程,直到线程达到线程池的最大值,一旦达到最大值就会保持不变
- 如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
-
5.1.3 CachedThreadPool
ExecutorService cachedThreadPool = Executors._newCachedThreadPool_();
- 创建一个可缓存的线程池,该线程池没有核心线程,创建新的线程之前会查看是否有以前使用的线程
- 如果线程池的大小超过了处理任务所需要的线程,就会回收部分空闲(默认 60 秒不执行任务)的线程
- 当任务数增加时,此线程池又可以智能的添加新线程来处理任务
- 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者JVM)能够创建的最大线程
-
5.2 ScheduleThreadPoolExecutor
继承自
ThreadPoolExecutor
所以满足线程池的一切特性- 支持延迟执行任务、周期性的执行任务
- 具体查看
java-base
项目 它和
Timer
的区别jdk1.7
开始提供的一个新的线程池- 把一个大的任务拆分成若干个小的任务(
fork
过程)把每个小任务的结果进行汇总(join
的过程) 比较适合分而治之,递归计算的
cpu
密集场景,实现工作窃取算法6. 线程池的实际应用
Rabbit MQ
可靠性消息投递组件中的消息发送使用多线程异步发送Redis Lock + ScheduledThreadPool
,实现简单版分布式定时任务,实现定时推送未结案的赔案Spring Task
也是用的线程池实现的定时任务Eureka Client
里面,每隔三十秒发送个心跳,其实就是个定时任务型的线程池-
7. 线程池的调优
7.1 线程池优化方向
线程数:关注如何设置线程数即可
工作队列的大小
字母代表的含义
- n:集群cpu核心数
- u:期望的cpu利用率
- wt:线程等待时间
- st:线程运行时间
- cpu密集型任务:大量的计算、排序….
- 线程数:经验公式 n + 1
- io密集型任务:操作数据库的过程、大量增删改查
- 线程数:经验公式 2n
- 混合型任务:上面两种都占有
- 线程数
n * U * (1+ wt/st )
- 线程数
通过
jvisualvm
分析项目运行评估业务是哪种类型的,给出期望优化
- 结合压测 + 计算出的理论值,不断在周边调整参数,结合坐标图得出
8. 异步化
第一个:同步阻塞
第二个:异步费阻塞
如何实现本地异步操作:
新搞一个线程()
线程池
@Async:spring提供的,使用动态代理 + 线程池实现的
消息队列