线程池概述
一种池化技术,减少线程频繁创建/销毁带来的性能开销
线程池优点:
降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。提高响应速度:任务到达时,无需等待线程创建即可立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
Executor
接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理; 内部通过维护一个阻塞队列存放任务,然后由线程池去自主获取任务。
线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)
线程池参数
直接new ThreadPoolExecutor()对象,并且手动的指定对应的参数
corePoolSize:线程池的核心线程数量 线程池创建出来后就会 new Thread() 5个
maximumPoolSize:最大的线程数量,线程池支持的最大的线程数
keepAliveTime:存活时间,当线程数大于核心线程,空闲的线程的存活时间 8-5=3
unit:存活时间的单位
BlockingQueue<Runnable> workQueue:阻塞队列 当线程数超过了核心线程数据,那么新的请求到来的时候会加入到阻塞的队列中
new LinkedBlockingQueue<>() 默认队列的长度是 Integer.MAX 那这个就太大了,所以我们需要指定队列的长度
threadFactory:创建线程的工厂对象
RejectedExecutionHandler handler:当线程数大于最大线程数的时候会执行的淘汰策略
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5
, 100
, 10
, TimeUnit.SECONDS
, new LinkedBlockingQueue<>(10000)
, Executors.defaultThreadFactory()
, new ThreadPoolExecutor.AbortPolicy()
);
线程池执行流程
- 首先根据 corePoolSIze创建工作线程
- 当并发量超过核心线程数,将多余的任务放如任务队列
- 如果任务队列满了,判断并发量是否超过最大线程数量,如果没有创建新的线程执行任务;
- 当任务处理完成,超过核心线程数量的线程会根据TimeUtil,过期销毁
- 如果启动新线程后仍处理不了任务,多余的任务根据拒绝策略进行其他操作
JDK中提供的几种线程池
- newCachedThreadPool
带有缓存的线程池,会在需要线程资源的时候进行创建,注意这里的最大线程数是Integer.MAX_VALUE
线程休眠时间为60s,超过时间会进行销毁。
一般用于执行短生命周期的异步任务
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newSingleThreadExecutor
单例线程池,线程池中时刻保证工作线程为1, 串行执行任务。
不同于fixedPool, 他保证返回的Executor不会创建额外的线程
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
- newScheduledThreadPool
具有定时任务的线程池,能够在延迟一定时间或定期执行任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// new ScheduledThreadPoolExecute()
// 创建一个延迟队列
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
二、线程池设计
- 任务调度
任务的来源一般为两个:第一种是任务直接被线程进行处理;第二种是核心线程已满,将后续的任务先放入阻塞队列,等待线程空闲再从队列中获取。
任务缓冲就是这里的阻塞队列。当阻塞队列,最大线程数都到达极限的时候,会根据拒绝策略保证线程池的安全。
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
- 线程管理
线程池中使用Worker维护线程管理,实现了Runnable
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}
thread为工作线程,使用ThreadFactory进行创建; firstTask为任务列表,如果firstTask不为空,说明当前线程可以直接处理任务,否则会创建一个队列,等待非核心线程创建去处理。
这里的Worker实现了AQS,使用不可重入的特性去反应线程现在的执行状态。
lock方法一旦获取了独占锁,表示当前线程正在执行任务中。
如果正在执行任务,则不应该中断线程。
如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。
线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
- 线程的增加 addWorker, 参数firstTast, core, 表示新创建的线程执行的第一个任务,可以为空; core为booelan类型标识当前线程是否为核心线程。
该方法只用来创建一个新的线程,具体的调用逻辑由上层实现。
- 线程的销毁,线程池中资源的管理由JVM进行垃圾回收,在线程池内部会通过一个hashMap维护引用,当销毁的时候只要消除引用即可。
由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程。
- Worker工作流程:线程中的run() 方法会执行具体的工作流程,首先判断当前是否有任务,如果没有,执行销毁线程的逻辑(如果小于核心线程会保留),否则利用锁机制保证执行任务。
三、 线程池的应用
3.1 快速响应请求
在访问一个网页的时候伴随许多资源的加载,此时可以使用多线程将资源(图片,文字,视频等)并发的执行。
而为了追求快速响应,不应该设置缓冲队列,而是调高核心线程数量和最大线程数量。