线程池
1、什么时候使用线程池?
- 单个任务处理时间比较短
- 需要处理的任务数量很大
2、线程池优势
- 重用存在的线程、减少线程创建、消亡的开销、提高性能
- 提高响应速度
- 提高线程的可管理性
3、线程池重要属性
- ctl:记录当前线程池的运行状态和线程池内有效线程的数量,高3位runState,低29位保存workerCount
- runStateOf:当前线程的状态
- workerCountOf:工作线程的数量
4、线程池五种状态
- RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务
- SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
- STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程
- TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,会去调用terminated()钩子函数
- TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做
5、工具类
ThreadPoolExecutor
- corePoolSize:核心线程数量
- maximumPoolSize:最大线程数量(默认Integer.MAX)
- workQueue:等待的队列,当线程池中的线程数量大于corePoolSize的时候,就把该任务封装成Worker对象放入等待队列
- keepAliveTime:如果线程池中的线程大于了核心线程数,如果没有继续提交任务,核心外的线程不会立即销毁,而是等到时间超过了keepAliveTime
- TimeUnit:时间级别
- threadFactory:用来创建新的线程
- handler:表示线程池的饱和策略,如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
ScheduledThreadPoolExecutor
- SingleScheduledThreadPool:创建单个定时线程池
- ScheduledThreadPool:创建定时线程池
6、线程池的四种队列
ArrayBlockingQueue
特点:
数组支持的有界队列,如果初始化为1,不能扩容,只能等队列被消费了之后,才能继续添加,如果满了会将线程阻塞。
线程池任务提交原理
当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会根据拒绝策略来进行处理。
LinkedBlockingQueue
链接节点支持的可选有界队列(也可以看成无界队列),当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。maximumPoolSize会失效
PriorityBlockingQueue
优先级支持的无界优先级队列
DelayQueue
优先级堆支持的、基于时间的调度队列
SynchronousQueue
特点:
是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素
7、Executors的使用
newSingleThreadExecutor
只有一个可重复执行的线程
newFixedThreadPool
创建使用固定线程数的FixedThreadPool,适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器
- corePoolSize:初始化的参数
- workQueue:使用无界队列LinkedBlockingQueue链表阻塞队列(链表阻塞队列,使用put方法插入数据的时候如果满了,会一直等待空间,加入元素)
- keepAliveTime = 0:因为使用了阻塞队列,任务超过了核心线程数,后续的任务都会添加到队列当中,这时maximunPoolSize就会失去意义
newCachedThreadPool
创建一个会根据需要创建新线程的,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
- keepAliveTime=60秒:没有核心线程数,设置空闲60秒,超过后就会销毁线程
- workQueue=SynchronousQueue:不存储元素的阻塞队列必须等待一个take操作
newScheduledThreadPool
- workQueue=delayWorkQueue:使用延迟队列作为缓存队列
- schedule(Callable callable, long delay, TimeUnit unit)方法
- callable:提交Callable或者Runnable任务
- period:表示连个任务连续执行的时间周期,第一个任务开始到第二个任务的开始,包含了任务的执行时间,该方法在initialDelay时间后开始周期性的按period时间间隔执行任务
- scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法
注意:创建的线程池实际上以不同的参数来创建的,创建出的线程池返回的都是ThreadPoolExecutor对象
注意:使用Executors创建线程池会出现内存溢出的问题,阿里巴巴规范指出,Executors创建线程池的默认最大容量是Integer.Max
问题归纳
1.FixedThreadPool
问题:并发量高的情况下容易出现线程池死锁的问题。
场景:父线程A(包括子线程1、子线程2)、父线程B(子线程3、子线程4)使用同一个固定线程池提交任务
故障:当线程池中全是父任务时,A和B。队列中存放的全是子线程,而子线程如果处理不结束,那么A和B线程也就不会结束,就会一直阻塞住,所以就会导致线程池死锁,并且因为FixedThreadPool采用的是无界队列,消息都会积压,队列就会越来越大
解决方案1:子任务在新的线程池中运行
解决方案2:采用有界队列,拒绝策略使用MQ来做降级处理