ScheduledThreadPoolExecutor详解

288 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

一、什么是ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但 ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而 ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数

public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {}

ScheduledThreadPoolExecutor 的状态管理、入队操作、拒绝操作等都是继承于 ThreadPoolExecutor;ScheduledThreadPoolExecutor 主要是提供了周期任务延迟任务相关的操作。

image.png

image.png

二、ScheduledThreadPoolExecutor是如何工作的

1、ScheduledThreadPoolExecutor的执行主要分为两部分

A、当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate() 方法或者scheduleWithFixedDelay() 方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask

B、线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

2、主要方法介绍

无返回值的延迟任务 schedule(Runnable command, long delay, TimeUnit unit) 这个方法的意思是在指定延迟之后运行task。这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例。

有返回值的延迟任务 schedule(Callable callable, long delay, TimeUnit unit)

这个方法与schedule (Runnable task)类似,也是在指定延迟之后运行task,不过它接收的是一个Callable实例,此方法会返回一个ScheduleFuture对象,通过ScheduleFuture我们可以取消一个未执行的task,也可以获得这个task的执行结果。

固定频率周期任务 scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

这个方法的作用是周期性的调度task执行。task第一次执行的延迟根据initialDelay参数确定,以后每一次执行都间隔period时长。

如果task的执行时间大于定义的period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。

固定延迟周期任务scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

scheduleWithFixedDelay的参数和scheduleAtFixedRate参数完全一致,它们的不同之处在于对period调度周期的解释。在scheduleAtFixedRate中,period指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。而在scheduleWithFixedDelay中,period指的当前任务的结束执行时间到下个任务的开始执行时间。

3、ScheduledThreadPoolExecutor的实现

ScheduledThreadPoolExecutor会把待调度的任务ScheduledFutureTask放到一个DelayQueue中。

ScheduledFutureTask主要包含3个成员变量:

  • long型成员变量time,表示这个任务将要被执行的具体时间
  • long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号
  • long型成员变量period,表示任务执行的间隔周期
    private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
      private final long sequenceNumber;  // 任务序号,从 AtomicLong sequencer 获取,当延迟时间相同时,序号小的先出
      private long time;                  // 下次任务执行时间
      private final long period;          // 0 表示非周期任务,正值表示固定频率周期任务,负值表示固定延迟周期任务
      RunnableScheduledFuture<V> outerTask = this;  // 重复执行的任务,传入的任务可以使用 decorateTask() 重新包装
      int heapIndex;                      // 队列索引
    }

DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumner小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

image.png

线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间; 线程1执行这个ScheduledFutureTask; 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间; 线程1把这个修改time之后的ScheduledFutureTaks放回DelayQueue中(DelayQueue.add())。