一、前言
springboot的常见的任务调度
这篇文章介绍了一些常见的任务调度技术方法。接下来,将会对ThreadPoolTaskScheduler、ScheduledThreadPoolExecutor进行对比。通过简单使用、源码实现、优缺点等来分析,以便在各种应用场景上使用相应技术。
说明:下面介绍的技术主要是针对 非分布式环境下可以考虑采用。
ScheduledThreadPoolExecutor适用场景:主要是延迟一次性任务、周期性任务;
ThreadPoolTaskScheduler适用场景:延迟任务、带有定时计划【可用cron表达式表示】的周期性任务;
【备注:在分布式环境下,如果要求延迟任务可参考实现延迟队列业务的场景该文章;如果要求周期性任务/定时任务任务调度@Scheduled、ScheduledThreadPoolExecutor、quartz、xxl-job可参考文章的quartz/xxl-job等分布式组件】
二、ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor类主要借助ThreadPoolExecutor的延迟阻塞队列DelayedWorkQueue
(1)、支持提交延迟任务schedule()、周期性任务scheduleAtFixedRate()或scheduleWithFixedDelay();
2.1、提交延迟任务schedule()
这个主要是延迟性任务调用方法。ScheduledThreadPoolExecutor.schedule会将用户任务包装为ScheduledFutureTask。ScheduledFutureTask是FutureTask的子类,所以有异常也是不抛出,被记录下来。
schedule()源码如下:
scheduler.schedule(new Runnable() {
@Override
public void run() {
//只是延迟,然后执行一次
System.out.println("addDelayTask start."+ TimeUtil.currentDatetime());
}
},10,TimeUnit.SECONDS);
ScheduledThreadPoolExecutor类的
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//将外部传入的任务封装成延迟任务对象ScheduledFutureTask
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t); //后面介绍
return t;
}
triggerTime()计算延迟时间,触发当前任务执行的时间----【当前时间 + 延迟时间】
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
2.1.1、延迟执行任务delayedExecute方法
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task); //如果是被中断,那么按照拒绝策略实行
else {
super.getQueue().add(task); //向阻塞队列中添加任务
//线程池状态为 SHUTDOWN 并且不允许执行任务了,就从队列删除该任务,并设置任务的状态为取消状态
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart(); //进入ThreadPoolExecutor类逻辑
}
}
进入ThreadPoolExecutor类的,下面主要是 addWorker()方法,就是添加线程到线程池,返回 true 表示创建 Worker 成功,且启动线程
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
2.2、延迟任务队列delayedQueue
前面addWorker()方法将工作线程加入线程池,按照线程池原理,它是阻塞在获取数据的take()方法。
—因此,重点关注下DelayedWorkQueue队列下,如何获取任务?
可参考queue队列解析 有说明延迟队列的原理。
2.2.1、DelayedWorkQueue#take()方法获取延迟任务
该方法主要获取延迟队列中任务延迟时间小于等于0 的任务。
如果延迟时间不小于0,那么调用条件队列的awaitNanos(delay)阻塞方法等待一段时间,等时间到了,延迟时间自然小于等于0了。
获取到任务后,工作线程就可以开始执行调度任务了。
-------从前面可以看出,我们将task线程封装成ScheduledFutureTask对象,那么,从延迟队列拿到数据,就开始执行run()方法。
2.2.2、ScheduledFutureTask#run()方法运行任务
public void run() {
//判断任务是否是周期性任务还是非周期性任务
boolean periodic = isPeriodic();
//是否是周期性任务,且检测当前状态能否执行,不能执行就取消
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic) //非周期任务,直接调用 FutureTask#run 执行一次
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
//周期任务的执行
setNextRunTime(); //设置周期任务的下一个执行时间
// 任务的下一次执行安排,如果当前线程池状态可以执行周期任务,加入队列,并开启新线程
reExecutePeriodic(outerTask);
}
}
ScheduledFutureTask.super.runAndReset()执行周期性任务,周期任务正常完成后任务的状态不会变化,依旧是 NEW;
如果本次任务执行出现异常,会进入 setException 方法将任务状态置为异常,后续的该任务将不会再周期的执行;
2.3、周期性任务scheduleAtFixedRate()
关键源码如下:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();