前言
ScheduledThreadPoolExecutor 继承于ThreadPoolExecutor,实现了ScheduledExecutorService。因此这个类是两种父类的合并。关于这个类,有几个疑问:
(1) 作为子类,它解决了父类ThreadPoolExecutor什么问题
(2) 它是怎么来解决的
(3) 注意事项
带着这几个疑问,开始了本章解析之旅。
ThreadPoolExecutor
这个类解决了线程在创建与销毁时的开销问题,达到了线程复用的效果。
一个任务提交给ThreadPoolExecutor以后,当可以运行时任务会立即运行。可能设计者当初并没有想到会有让任务延迟几分钟或者指定时间后再运行这种场景,于是ScheduledThreadPoolExecutor作为ThreadPoolExecutor的扩展,来弥补了这种场景需求,让它可以达到延迟执行任务或者定时执行任务,周期性执行任务等。那么它是怎么来实现的呢?
ScheduledThreadPoolExecutor
为了解决能够让任务达到延迟执行或者周期性执行的目的,Java提供了ScheduledThreadPoolExecutor,该类继承了ThreadPoolExecutor的功能和特性,并且拓展了延迟和周期性调度。
首先区别一下与ThreadPoolExecutor在构造上的不同:
从上两幅图对比可以看出,两者的不同在于队列和队列中的任务不同。
因此本章只做ScheduledFutureTask 、DelayedWorkQueue以及重要方法等重要部分解析原理。
一、 DelayedWorkQueue
该队列是一个无界优先级队列,其底层是一个RunnableScheduledFuture的数组,初始大小为16。因此这表明该数组不仅可以存储ScheduledFutureTask任务,还可以存储RunnableScheduledFuture其他的子类。
有提到两个关键词: 无界 、 优先级.
它们怎么实现的呢?首先看无界,底层是一个数组结构,当添加新任务时会执行offer()方法,offer()方法会调用grow()自动扩容, 每次扩容50%。
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
}
再看优先级。
JDK源码上说该队列具有堆的特征,它的很多方法就是将这底层数组按堆的形式进行操作。具体的操作方法不再探究。
需要注意的是,为什么要设计成堆的特性,以及ScheduledThreadPoolExecutor线程池为什么专门设计出这个队列?
我想这是有原因的。
- 首先ScheduledThreadPoolExecutor线程池要满足存储延迟任务的需求,也要满足储存即时任务的需求,多样化的任务导致了存储任务的容器必定不平常,因此需要定制。
- 在多样化的任务类型面前,如何有序的满足任务被线程消费,并且高效的,也是很重要的。因此需要定制。
以上两点,有几个关键词: 多样化任务类型、有序、高效.
DelayedWorkQueue被设计成RunnableScheduledFuture数组的原因是为了满足任务多样化。
当有延迟、非延迟或周期性任务时,任务的执行顺序会随时因为客户端的添加和删除操作而导致顺序发生改变,所以该队列会频繁的变更执行顺序。设计优先级以满足任务的先后执行非常重要。
而DelayedWorkQueue被设计成堆的原因是为了满足操作性能上的高效,这一点通过源码可以看到。
JDK有特别注释:
DelayedWorkQueue基于类似于DelayQueue和PriorityQueue中的基于堆的数据结构,除了每个ScheduledFutureTask也将其索引记录到堆阵列中。
* 这消除了在取消时需要找到任务,大大加快了删除(从O(n)到O(log n)),并且减少了垃圾保留,否则通过等待元素在清除之前上升到顶部而发生。
* 但是因为队列也可能包含不是ScheduledFutureTasks的RunnableScheduledFutures,我们不能保证有这样的索引可用,在这种情况下我们回到线性搜索。
* (我们期望大多数任务不会被装饰,并且更快的情况将是更常见的。)。
该线程池如何满足任务有序被线程消费呢?可以看下面特定任务的结构。
二、 ScheduledFutureTask
该对象继承了FutureTask并且实现了RunnableScheduledFuture,所以它也是Runnable的一个子类,每种线程池都有自己特定的任务范围。而ScheduledFutureTask就属于 ScheduledThreadPoolExecutor线程池指定的任务类型。这种任务我的理解有两种特性。
ScheduledFutureTask 有几个重要的参数。
private final long sequenceNumber; // 每个任务的唯一序列号
private long time; // 纳秒级时间
private final long period; // 是否周期性任务
int heapIndex; // 队列索引(支持快速删除)
ScheduledFutureTask 实现有序的重要方法。compareTo
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
任务之间通过compareTo方法进行比较,再进行堆排序,把最先应该消费的任务排在前面。compareTo通过与自己这个ScheduledFutureTask任务作比较。
对于比较方是ScheduledFutureTask任务:
1. 纳秒级时间
2. 唯一序列号的大小
对于比较方是其他RunnableScheduledFuture任务:
1. 纳秒级时间
三、重要参数
-
continueExistingPeriodicTasksAfterShutdown
关闭线程池后,是否继续执行周期性任务. 这个参数只和与周期性方法scheduleAtFixedRate()和周期性方法scheduleWithFixedDelay()有关。 参数有两个值,True和False. True表示: 等待中或运行中的任务将会在线程池Shutdown()方法执行后继续循环运行。 False表示:等待中的任务在Shutdown()后将不会继续执行,而运行中的任务会继续执行,直至所有运行中的任务完成后关闭线程池。
-
executeExistingDelayedTasksAfterShutdown
关闭线程池后,是否继续执行延迟任务. 这个参数只和提交延迟任务的schedule()方法有关.参数有两个值,True和False. True表示:线程池Shutdown()后,线程池将会继续执行所有等待中和运行中的延迟任务,所有延迟任务完成后再关闭线程池。 False表示:线程池Shutdown()后,线程池将会继续执行运行中的任务再关闭,但不会再执行等待中的任务。
-
allowCoreThreadTimeOut
继承自ThreadPoolExecutor, 这个参数在JDK中有特别注意事项. * While this class inherits from {@link ThreadPoolExecutor}, a few * of the inherited tuning methods are not useful for it. In * particular, because it acts as a fixed-sized pool using * {@code corePoolSize} threads and an unbounded queue, adjustments * to {@code maximumPoolSize} have no useful effect. Additionally, it * is almost never a good idea to set {@code corePoolSize} to zero or * use {@code allowCoreThreadTimeOut} because this may leave the pool * without threads to handle tasks once they become eligible to run.
意思是不能将corePoolSize设置为零或者将参数allowCoreThreadTimeOut设置为true,这样会导致任务没有线程来执行,作者亲测这样做线程池会抛出异常。
-
removeOnCancel
任务被关闭后,是否需要从队列中移除.
这个参数是ScheduledThreadPoolExecutor特有的,这也是为什么要在任务ScheduledFutureTask 上加上index索引了,当删除时跟着索引可以很快的找到指定任务并删除。
注意: 与Shutdown()不同的是,shutdownNow()将会中断一切类型和状态下的任务, 不受任何参数控制,并且关闭线程池。
四、 提交任务的方法对比
更重点来了,几种核心任务提交方法如下:
1. execute(Runnable command)
2. submit(Runnable task)
3. submit(Callable<T> task)
4. submit(Runnable task, T result)
5. schedule(Runnable command, long delay, TimeUnit unit)
6. scheduleCallable<V> callable,long delay, TimeUnit unit)
7. scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
8. scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
-
execute(Runnable command)
特点: 立即执行,无延时. -
submit(Runnable task)
特点: 立即执行,无延时。集成了future模式,可以返回一个future对象,用于异步控制任务。 -
submit(Callable task)
特点:立即执行,无延时。集成了future模式,可以返回一个future对象,用于异步控制任务。当完成时可以有返回值。 -
submit(Runnable task, T result)
特点: 立即执行,无延时。集成了future模式,可以返回一个future对象,用于异步控制任务。当完成时也可以有返回值,就是设置进去的result. -
schedule(Runnable command, long delay, TimeUnit unit)
特点: 延迟执行,延时可设置。集成了future模式,可以返回一个future对象,用于异步控制任务。 -
scheduleCallable callable,long delay, TimeUnit unit)
特点: 延迟执行,延时可设置。集成了future模式,可以返回一个future对象,用于异步控制任务。完成时有返回值。 -
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
特点: 周期性执行,间隔时间计算起点是开始运行时,循环间隔时间可设置。集成了future模式,可以返回一个future对象,用于异步控制任务。 -
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
特点: 周期性执行,间隔时间计算起点是结束运行时,循环间隔时间可设置。集成了future模式,可以返回一个future对象,用于异步控制任务。
总结
以上就是ScheduledThreadPoolExecutor的理解,如有不对请多多斧正。