线程池
线程池的优势
- 线程是一种昂贵的资源.创建和销毁都是非常昂贵的.所以需要线程池来更好的减少和复用线程
- 提前初始化一部分线程,可以跳过从头创建从而快速申请到执行
- 对线程的总体数量进行把控,避免过度开启线程
线程池的原理和执行流程
线程池一般由 工作线程池 和 阻塞队列 两大部分组成
- 工作线程池
包含线程池中实际执行的所有线程.一般会有初始数量(期望数量)和最大数量对其进行控制 - 阻塞队列
所有执行单元的提交,都会由阻塞队列进行暂存.阻塞队列一般有规定上限,以及达到上限如何处理的拒绝策略等
所有整个线程池的执行流程是
- 初始化线程池.可选提前创建部分线程(接近期望数量)
- 等待接受执行单元.
一旦收到执行单元就会查看当前工作线程池的数量
1.如果在期望数量之下,无论是否有空闲线程都会创建新线程并执行.(最终达到期望数量)
2.如果在期望数量和最大数量之间,就会尝试寻找空闲线程.如果能找到空闲线程就会直接执行,如果不能找到空闲线程就会创建新线程
3.如果在最大数量之上,就会交给阻塞队列 - 执行单元交给阻塞队列后,????
- 然后按照阻塞队列的策略(例如FIFO),在工作线程池中找一个空闲的线程进行执行. 如果工作线程池中找到一个空闲线程,就会标记这个线程并使用这个线程进行执行
- 工作线程池本身会定时检查空闲线程,如果达到最大空闲时间依然没有任务执行就会销毁,直到工作线程池数量最终慢慢回归到期望数量
线程池大小设置
根据实际线程池的使用场景决定
- 如果是CPU密集型 就应该适当的减少线程池数量.
因为此时提高线程数量无助于提升执行效率,反而因为过多的线程切换影响效率 - 如果是IO密集型 就可以适当的提高线程池数量
因为在IO密集的等待时间中,并行的尝试执行其它的任务
线程池的实现
ThreadPoolExecutor
线程池签名
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
线程池的期望线程数.在空闲时,线程池最终会慢慢回到期望线程数
maximumPoolSize
线程池最大线程数
keepAliveTime & unit
空闲线程的保留时间 & 空闲线程保留时间的单位
超出期望线程数(CorePoolSize)的线程,会在该时间后被自动销毁
workQueue
阻塞队列
每个提交的执行单元都会进入阻塞队列缓存,再由线程池决定何时使用哪个空闲线程进行执行
阻塞队列可以设置上限,在达到上限后继续进行提交就会触发拒绝策略
阻塞队列有几个可选实现:
- ArrayBlockingQueue 有界队列 由数组实现的阻塞队列,FIFO.数组实现,需要指定队列数量,是为有界队列
- LinkedBlockingQueue 无界队列
由链表实现的阻塞队列,FIFO,链表实现,默认以Integer.MAX_VALUE,是为无界队列 - SynchronousQueue 无队列(无界队列)
不使用(上限为0)的阻塞队列,当提交后将立即寻找一个工作线程执行,如果没有找到会立即创建 - PriorityBlockingQueue
按照优先级阻塞
threadFactory
线程创建工厂
如果线程池没有达到上限又提交了新的执行单元,就会使用该工厂创建一个线程加入线程池并执行
RejectedExecutionHandler handler
执行拒绝策略
在提交执行单元到阻塞队列时,如果达到阻塞队列上限,就按该拒绝策略决定如何执行
可选的拒绝策略有
- AbortPolicy(默认)
拒绝后直接抛出异常告知调用者 - CallerRunsPolicy 使用调用者
拒绝后不使用线程池执行,而是交回调用者线程池执行 - DiscardOldestPolicy 抛弃队列中最久的任务
拒绝后丢弃最老的执行单元,然后重新加入队列 - DiscardPolicy 抛弃当前任务
拒绝后直接丢弃当前任务.不抛出异常
线程池的关闭
shutdown & shutdownNow
都会遍历所有线程interrupt中断线程
区别
- shutdown
仅停止阻塞队列中等待的线程,而正在执行中的线程会等待完成 - shutdownNow
会强行停止所有的线程
其它线程池实现
JDK还提供其它线程池共计四种
- FixedThreadPool
- CachedThreadPool
- SingleThreadExecutor
- ScheduledThreadPool
这四种线程池本质都是ThreadPoolExecutor策略切换(参数切换实现切换)
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 期望数量与最大数量都是nThreads的固定线程数量线程池. 这样线程池中线程数量实际将始终等于最大数量(或者说期望数量)
- 没有过期时间,但实际其实无效.因为期望数量与最大数量相同,这样线程空闲也不会销毁
- LinkedBlockingQueue 阻塞队列为无界队列,FixedThreadPool允许无限缓存等待执行单元
也就是它不会拒绝任务
CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 期望数量为0,最大数量为Integer.MAX_VALUE,说明线程数量是运行无限开启的,但允许销毁
- 60秒过期,说明所有空闲线程最终会在60秒后全部被销毁
- SynchronousQueue 也是一个无界队列.说明它也不会拒绝任务
每一个任务都会使用空闲或者创建一个新线程来执行
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 期望数量为1,最终数量为1. 说明线程池线程数量固定为1,并且也不会销毁
- LinkedBlockingQueue 无界队列,也不会拒绝任务.
ScheduledThreadPool
两种定期间隔调度策略
- scheduleWithFixedDelay
以间隔频率调度执行.(会在任务真正执行完毕后开始计算间隔时间再执行) - scheduleAtFixedRate
以固定频率调度执行.(不会考虑实际的任务执行,可能会重复同时执行)
内部封装了
线程池实现的选择
按照阿里的开发规范,不建议使用四种默认实现而始终使用ThreadPoolExecutor本身
这样的目的是通过手动调整参数来完全达到自己的目的,避免性能问题
- CachedThreadPool
可以无限创建线程,非常容易出问题 - FixedThreadPool,SingleThreadExecutor
这两个虽然都不会不限创建线程,但是其阻塞队列是无界的,无限缓存而不拒绝可能会造成OOM - ScheduledThreadPool
内部依然是可以无限创建线程 …..????
https://zhuanlan.zhihu.com/p/33264000 https://blog.csdn.net/qq_25806863/article/details/71126867 https://blog.csdn.net/wang_rrui/article/details/78541786