为什么要有线程池
- 线程池能够对线程进行统一分配,调优和监控:
- 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
- 提高响应速度(无须创建线程)
- 提高线程的可管理性
如何实现和管理线程池的
从JDK 5开始,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制由Executor框架提供。Executors 类提供了使用了 ThreadPoolExecutor 的简单的 ExecutorService 实现。
但是 ThreadPoolExecutor 提供的功能远不止于此。我们可以在创建 ThreadPoolExecutor 实例时指定活动线程的数量,我们也可以限制线程池的大小,并且创建自定义 RejectedExecutionHandler 实现来处理不能适应工作队列的工作。
ThreadPoolExecutor 提供了一些方法,我们可以使用这些方法来查询 executor 的当前状态,线程池大小,活动线程数量以及任务数量。另外executor 的活动任务、完成任务以及所有完成任务,这些数量上的变化。我们可以调用 shutdown() 方法来结束所有提交的任务并终止线程池
使用详解
**ThreadPoolExecutor Java线程池就是一个线程集合workerSet和一个阻塞队列workQueue。**当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
线程池工作原理
1)新任务通常是对任务队列形式进来的,遵循先进先出原则。线程池监听到有任务到来,会创建线程来执行任务队列中的任务。
2-1)当执行任务的线程数达到核心线程数的时候,再过来新的任务,会创建额外的线程进行处理,并且会给额外的线程连接设置存活时间。
2-2)当队列中的任务被消费完毕之后,超出核心任务数建立的额外线程,会在超过keepAliveTime后自动释放。
3)当执行任务的线程数是否超过最大线程数之后,会执行饱和策略。
线程池的创建
1)构造方法实现(ThreadPoolExecutor)
ThreadPoolExecutor 继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口
2)Executor 框架的工具类 Executors 来实现
2-1)FixedThreadPool
一个固定线程数量的线程池(无界线程池)。使用无界队列 LinkedBlockingQueue 队列的容量为 Integer.MAX_VALUE
2-2)SingleThreadExecutor
只有一个线程的线程池(无界线程池)。使用无界队列 LinkedBlockingQueue 作为线程池的工作队列
2-3)CachedThreadPool
根据实际情况调整线程数量的线程池。corePoolSize 如果被设置为空(0),maximumPoolSize会被默认设置为 Integer.MAX.VALUE
核心参数
corePoolSize 线程池的核心线程数
maximumPoolSize 最大线程数
keepAliveTime 线程的存活时间
unit keepAliveTime的单位
workQueue 一个阻塞队列,提交的任务将会被放到这个队列里
threadFactory 线程工厂,用来创建线程
handler 饱和策略/拒绝策略
配置线程池需要考虑因素
从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系这四个角度来分析。并且近可能地使用有界的工作队列。
性质不同的任务可用使用不同规模的线程池分开处理:
- CPU密集型: 尽可能少的线程,Ncpu+1
- IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池
- 混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。
扩展 ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,为任务提供延迟或周期执行,属于线程池的一种。
特性
- ScheduledFutureTask 专门的任务类型来执行周期任务,也可以接收不需要时间调度的任务(这些任务通过 ExecutorService 来执行)。
- DelayedWorkQueue 专门的存储队列来存储任务,DelayedWorkQueue 是无界延迟队列DelayQueue 的一种。相比ThreadPoolExecutor也简化了执行机制(delayedExecute方法,后面单独分析)。
- 支持可选的run-after-shutdown参数,在池被关闭(shutdown)之后支持可选的逻辑来决定是否继续运行周期或延迟任务。并且当任务(重新)提交操作与 shutdown 操作重叠时,复查逻辑也不相同。
数据结构
ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor
。但内部构造了两个内部类 ScheduledFutureTask
和 DelayedWorkQueue
:
ScheduledFutureTask
: 继承了FutureTask,说明是一个异步运算任务;最上层分别实现了Runnable、Future、Delayed接口,说明它是一个可以延迟执行的异步运算任务。DelayedWorkQueue
: 这是 ScheduledThreadPoolExecutor 为存储周期或延迟任务专门定义的一个延迟队列,继承了 AbstractQueue,为了契合 ThreadPoolExecutor 也实现了 BlockingQueue 接口。它内部只允许存储 RunnableScheduledFuture 类型的任务。与 DelayQueue 的不同之处就是它只允许存放 RunnableScheduledFuture 对象,并且自己实现了二叉堆(DelayQueue 是利用了 PriorityQueue 的二叉堆结构)。
内部类ScheduledFutureTask
//为相同延时任务提供的顺序编号
privatefinallong sequenceNumber;
//任务可以执行的时间,纳秒级
privatelong time;
//重复任务的执行周期时间,纳秒级。
privatefinallong period;
//重新入队的任务
RunnableScheduledFuture<V> outerTask =this;
//延迟队列的索引,以支持更快的取消操作
int heapIndex;
sequenceNumber
: 当两个任务有相同的延迟时间时,按照 FIFO 的顺序入队。sequenceNumber 就是为相同延时任务提供的顺序编号。time
: 任务可以执行时的时间,纳秒级,通过triggerTime方法计算得出。period
: 任务的执行周期时间,纳秒级。正数表示固定速率执行(为scheduleAtFixedRate提供服务),负数表示固定延迟执行(为scheduleWithFixedDelay提供服务),0表示不重复任务。outerTask
: 重新入队的任务,通过reExecutePeriodic方法入队重新排序。
核心方法run()
ScheduledFutureTask 的run方法重写了 FutureTask 的版本,以便执行周期任务时重置/重排序任务。**任务的执行通过父类 FutureTask 的run实现。**内部有两个针对周期任务的方法:
- setNextRunTime(): 用来设置下一次运行的时间
- reExecutePeriodic(): 周期任务重新入队等待下一次执行
reExecutePeriodic与delayedExecute的执行策略一致,只不过reExecutePeriodic不会执行拒绝策略而是直接丢掉任务。
cancel方法
本质上由其父类 FutureTask.cancel 实现。取消任务成功后会根据removeOnCancel参数决定是否从队列中移除此任务。
核心属性
continueExistingPeriodicTasksAfterShutdown
和executeExistingDelayedTasksAfterShutdown
是 ScheduledThreadPoolExecutor 定义的run-after-shutdown
参数,用来控制池关闭之后的任务执行逻辑。removeOnCancel
用来控制任务取消后是否从队列中移除。当一个已经提交的周期或延迟任务在运行之前被取消,那么它之后将不会运行。默认配置下,这种已经取消的任务在届期之前不会被移除。 通过这种机制,可以方便检查和监控线程池状态,但也可能导致已经取消的任务无限滞留。为了避免这种情况的发生,我们可以通过setRemoveOnCancelPolicy
方法设置移除策略,把参数removeOnCancel
设为true可以在任务取消后立即从队列中移除。sequencer
是为相同延时的任务提供的顺序编号,保证任务之间的 FIFO 顺序。与 ScheduledFutureTask 内部的sequenceNumber参数作用一致。
构造函数
ScheduledThreadPoolExecutor 内部有四个构造函数,而构造函数都是通过super调用了ThreadPoolExecutor的构造,并且使用特定等待队列DelayedWorkQueue。
1、核心方法:Schedule
schedule主要用于执行一次性(延迟)任务。函数执行逻辑分两步:
封装 Callable/Runnable
: 首先通过triggerTime计算任务的延迟执行时间,然后通过 ScheduledFutureTask 的构造函数把 Runnable/Callable 任务构造为ScheduledThreadPoolExecutor可以执行的任务类型,最后调用decorateTask方法执行用户自定义的逻辑;decorateTask是一个用户可自定义扩展的方法,默认实现下直接返回封装的RunnableScheduledFuture任务执行任务
: 通过delayedExecute实现。
delayedExecute是执行任务的主方法,方法执行逻辑如下:
- 如果池已关闭(ctl >= SHUTDOWN),执行任务拒绝策略;
- 池正在运行,首先把任务入队排序;然后重新检查池的关闭状态,执行如下逻辑:
A
: 如果池正在运行,或者 run-after-shutdown 参数值为true,则调用父类方法ensurePrestart启动一个新的线程等待执行任务。ensurePrestart是父类 ThreadPoolExecutor 的方法,用于启动一个新的工作线程等待执行任务,即使corePoolSize为0也会安排一个新线程。
B
: 如果池已经关闭,并且 run-after-shutdown 参数值为false,则执行父类(ThreadPoolExecutor)方法remove移除队列中的指定任务,成功移除后调用ScheduledFutureTask.cancel取消任务
核心方法scheduleAtFixedRate/scheduleWithFixedDelay:
scheduleAtFixedRate传的是正值,
scheduleWithFixedDelay传的则是负值,这个值就是 ScheduledFutureTask 的period属性。
核心方法:shutdown()
shutdown方法中调用的关闭钩子onShutdown方法,它的主要作用是在关闭线程池后取消并清除由于关闭策略不应该运行的所有任务,这里主要是根据 run-after-shutdown 参数(continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown)来决定线程池关闭后是否关闭已经存在的任务。
为什么ThreadPoolExecutor 的调整策略却不适用于 ScheduledThreadPoolExecutor
由于 ScheduledThreadPoolExecutor 是一个固定核心线程数大小的线程池,并且使用了一个无界队列.设置corePoolSize为0或者设置核心线程空闲后清除(allowCoreThreadTimeOut)同样也不是一个好的策略,因为一旦周期任务到达某一次运行周期时,可能导致线程池内没有线程去处理这些任务。
Executors 提供了哪几种方法来构造 ScheduledThreadPoolExecutor?
- newScheduledThreadPool: 可指定核心线程数的线程池。可以通过setCorePoolSize方法来修改核心线程数
- newSingleThreadScheduledExecutor: 只有一个工作线程的线程池。如果内部工作线程由于执行周期任务异常而被终止,则会新建一个线程替代它的位置。且线程数不可扩展.