线程
操作系统调度运算的最小单位,开发过程中可以通过实例线程执行任务,实现并发的效果
线程池作用
1、有效的通过约束实现降低线程创建和销毁的资源消耗
2、提高任务响应速度,无需等待线程创建
3、提供线程统一的管理、监控和调优
ThreadPoolExecutor线程池参数
参数 | 作用 |
---|---|
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列 |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
handler | 线程池对拒绝任务的处理策略 |
拒绝策略handler
当线程池任务处理不过来的时候(什么时候认为处理不过来后面描述),可以通过handler指定的策略进行处理,ThreadPoolExecutor提供了四种策略:
拒绝策略类型 | 处理方式 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常。 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃队列队头的任务,然后重新尝试执行任务(重复此过程) |
ThreadPoolExecutor.CallerRunsPolicy | 由调用“当前线程”处理该任务 |
可以通过实现RejectedExecutionHandler接口自定义处理方式。
阻塞队列类型
队列名称 | 队列特征 |
---|---|
ArrayBlockingQueue | 这是一个由数组实现的容量固定的有界阻塞队列. |
SynchronousQueue | 没有容量,不能缓存数据;每个put必须等待一个take; offer()的时候如果没有另一个线程在poll()或者take()的话返回false。 |
LinkedBlockingQueue | 这是一个由单链表实现的默认无界的阻塞队列。LinkedBlockingQueue提供了一个可选有界的构造函数,而在未指明容量时,容量默认为Integer.MAX_VALUE。 |
PriorityBlockingQueue | 有序的阻塞队列,无界 |
DelayBlockingQueue | 每一个任务都可以设置一个延时时间,等待延时时间过了才能够被线程获取 |
阻塞队列基本操作如下
方法名 | 作用 |
---|---|
add | 增加一个元索; 如果队列已满,则抛出一个异常 |
remove | 移除并返回队列头部的元素; 如果队列为空,则抛出一个异常 |
element | 返回队列头部的元素; 如果队列为空,则抛出一个异常 |
offer | 添加一个元素并返回true; 如果队列已满,则返回false |
poll | 移除并返回队列头部的元素; 如果队列为空,则返回null |
peek | 返回队列头部的元素; 如果队列为空,则返回null |
put | 添加一个元素; 如果队列满,则阻塞 |
take | 移除并返回队列头部的元素; 如果队列为空,则阻塞 |
线程池执行方法
- Future submit(Callable c)
- Future submit(Runnable r,Object o)
- 该方法返回一个Future对象,可执行带返回值的线程;或者执行想随时可以取消的线程。
Future对象的get()(如果没有执行完任务,则阻塞当前线程)方法获取返回值。Future对象的cancel(true/false)取消任务,未开始或已完成返回false,参数表示是否中断执行中的线程
- void execute(Runnable r) 没有返回值,只能接收实现Runnable的任务。
- shutdown() 不接收新任务,会处理已添加任务
- shutdownNow() 不接受新任务,不处理已添加任务,中断正在处理的任务
执行过程图如下
1、如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
2、如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
4、当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
总结即:处理任务判断的优先级为 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
注意:
- 当workQueue使用的是无界限队列时,maximumPoolSize参数就变的无意义了,比如new LinkedBlockingQueue(),或者new ArrayBlockingQueue(Integer.MAX_VALUE);
- 使用SynchronousQueue队列时由于该队列没有容量的特性,所以不会对任务进行排队,如果线程池中没有空闲线程,会立即创建一个新线程来接收这个任务。maximumPoolSize要设置大一点。
- 核心线程和最大线程数量相等时keepAliveTime无作用.
默认的线程池
1) FixedThreadPool(固定大小线程池) 和 SingleThreadPool(单线程线程池):
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool (缓冲区线程池)和 ScheduledThreadPool(定时线程池):
允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。
使用情景模拟
(1)高并发、任务执行时间短的业务,线程池核心线程数可以设置为CPU核数+1,减少线程上下文的切换
(2)并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的核心线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦