任务调度之ScheduledThreadPoolExecutor
1、前言
ScheduledExecutorService在普通执行器接口(ExecutorService)的基础上引入了Future模式,使得可以限时或周期性地调度任务。
ScheduledThreadPoolExecutor其实是继承了ThreadPoolExecutor这个普通线程池,同时实现ScheduledThreadPoolExecutorService接口。ThreadPoolExecutor中提交的任务都是实现了Runnable接口,但是ScheduledThreadPoolExecutor比较特殊,由于要满足任务的延迟/周期调度功能,它会对所有的Runnable任务都进行包装,包装成一个RunnableScheduledFuture任务。
RunnableScheduledFuture是Future模式中的一个接口,在ThreadPoolExecutor中,需要指定一个阻塞队列作为任务队列。ScheduledThreadPoolExecutor中也一样,不过特殊的是,ScheduledThreadPoolExecutor中的任务队列是一种特殊的延时队列(DelayQueue)。ScheduledThreadPoolExecutor在内部定义了DelayQueue的变种——DelayedWorkQueue,它和DelayQueue类似,只不过要求所有入队元素必须实现RunnableScheduledFuture接口。
Delayed接口
// 继承Comparable接口,表示该类对象支持排序 public interface Delayed extends Comparable<Delayed> { // 返回该对象剩余时延 long getDelay(TimeUnit unit); }
Delayed接口很简单,继承了Comparable接口,表示对象是可以比较排序的。ScheduledFuture接口
// 仅仅继承了Delayed和Future接口,自身没有任何代码 public interface ScheduledFuture<V> extends Delayed, Future<V> { }
RunnableScheduledFuture接口
public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, Schedule // 是否是周期任务,周期任务可被调度运行多次,非周期任务只被运行一次 boolean isPeriodic(); }
ScheduledFutureTask类
回到 schecule 方法中,它创建了一个ScheduledFutureTask的对象,由上面的关系图可知,ScheduledFutureTask直接或者间接实现了很多接口,ScheduledFutureTask实现方法:
构造方法
ScheduledFutureTask(Runnable r, V result, long ns, long period) { // 调父类FutureTask的构造方法 super(r, result); // time表示任务下次执行的时间 this.time = ns; // 周期任务,正数表示按照固定速率,负数表示按照固定时延,0表示不是周期任务 this.period = period; // 任务的编号 this.sequenceNumber = sequencer.getAndIncrement(); }
Delayed接口的实现
// 实现Delayed接口的getDelay方法,返回任务开始执行的剩余时间 public long getDelay(TimeUnit unit) { return unit.convert(time - now(), TimeUnit.NANOSECONDS); }
2、实例
指定时间给发送消息。将消息(包含发送时间)存储在数据库中,然后实现一个定时任务,每隔1秒检查数据库在当前时间有没有需要发送的消息。
public class ScheduledTask { private static final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1, Executors.defaultThreadFactory()); private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args){ // 新建一个固定延迟时间的计划任务 executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { if (haveMsgAtCurrentTime()) { System.out.println(df.format(new Date())); System.out.println("大家注意了,我要发消息了"); } } }, 1, 1, TimeUnit.SECONDS); } public static boolean haveMsgAtCurrentTime(){ //查询数据库,有没有当前时间需要发送的消息 // 这⾥省略实现,直接返回true return true; } }