前言
在一开始学习定时任务的时候是使用的quartz来实现的。后来习惯于全注解的开发模式。在SpringBoot环境中,只需要在启动类上加上EnableScheduling
注解,然后在需要使用定时任务的方法上加上Scheduled
注解,当然方法所属的类需要在Spring环境中。在启动类中加上EnableScheduling
注解这一步是为了生成ScheduledAnnotationBeanPostProcessor
这个BeanPostProccessor
实现类,这个类用来对添加了Scheduled
注解的方法进行增强处理,调用相应的类来完成定时任务。这一步也可以换成配置一个Configuration类,总之,能把ScheduledAnnotationBeanPostProcessor
这个BeanPostProccessor
实现类注入到Spring环境中就好。
ScheduledAnnotationBeanPostProcessor
先来看这个关键的BeanPostProccessor
实现类,关于BeanPostProccessor
起作用的时机就不在赘述了,我们直接看这个类的postProcessAfterInitialization
方法
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
@Override
public Set<Scheduled> inspect(Method method) {
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
}
}
else {
// Non-empty set of methods
//拿到Bean中所有带有@Scheduled注解的方法。
for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
//处理注解的方法
processScheduled(scheduled, method, bean);
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
processScheduled
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
Assert.isTrue(method.getParameterTypes().length == 0,
"Only no-arg methods may be annotated with @Scheduled");
Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
//将方法的执行封装到一个Runnable实现类的run()方法中
Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);
// Determine initial delay
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
try {
initialDelay = Long.parseLong(initialDelayString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
}
}
// Check cron expression
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay < 0) {
initialDelay = 0;
}
// Check fixed delay
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
try {
fixedDelay = Long.parseLong(fixedDelayString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
// Check fixed rate
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
try {
fixedRate = Long.parseLong(fixedRateString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
}
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
if (registeredTasks == null) {
registeredTasks = new LinkedHashSet<ScheduledTask>(4);
this.scheduledTasks.put(bean, registeredTasks);
}
registeredTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
这个方法很长,但是没有难以理解的地方,但是看这个流程有点疑问,当我们在@Scheduled
上加了fixedDelay
参数和cron
参数后,难道会生成多个Task去执行吗?(后续测试一下)
这个方法最重要的地方就是通过registrar
注册Task,交给线程池处理。
以
this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay))
这个方法进行分析
registrar对应的是ScheduledTaskRegistrar
public ScheduledTask scheduleFixedRateTask(IntervalTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask();
newTask = true;
}
if (this.taskScheduler != null) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
//默认的taskScheduler是
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
}
else {
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
}
}
else {
addFixedRateTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
经过一系列的委托,最终发现调用的是ScheduledThreadPoolExecutor
里的方法。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
我们看到使用的workQueue与之前分析的线程池都不同,DelayedWorkQueue
是ScheduledThreadPoolExecutor
的一个内部类,有着自己特殊的实现。
我们以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();
//将Runnable实现类封装成ScheduledFutureTask(ScheduledThreadPoolExecutor的内部类)
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
//调用decorateTask方法,其实返回的也是sft,但是类型转化为父类RunnableScheduledFuture
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
//设置outerTask为自己
sft.outerTask = t;
//延迟执行
delayedExecute(t);
return t;
}
delayedExecute
private void delayedExecute(RunnableScheduledFuture<?> task) {
//如果线程池SHUTDOWN
if (isShutdown())
//选择拒绝策略拒绝任务,该任务的执行与否取决于具体的拒绝策略
reject(task);
else {
//先添加到workQueue中
super.getQueue().add(task);
//如果线程池SHUTDOWN或者不在RUN状态 就从workQueue移除该任务
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
//如果移除成功,则取消当前任务
task.cancel(false);
else
//否则就可以让当前任务准备执行
ensurePrestart();
}
}
ensurePrestart
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
//保证当前的Worker个数永远不会超过corePoolSize
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
这个方法就是执行addWorker
方法
但是一般情况下,是不会去添加的。因为通常来说ScheduledThreadPoolExecutor
的核心线程池不会太大(默认使用corePoolSize为1),一旦当前的Worker不小于核心线程池大小之后,就无法添加了。所以后续的任务都会添加到workQueue中。
ScheduledFutureTask
该类是ScheduledThreadPoolExecutor
的内部类,继承了FutureTask
。
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
//如果不是定时任务
else if (!periodic)
//调用FutureTask的run方法
ScheduledFutureTask.super.run();
//如果是定时任务,调用FutureTask的runAndReset方法,该方法不会设置返回值
else if (ScheduledFutureTask.super.runAndReset()) {
//设置下次执行的时间
setNextRunTime();
//继续执行这个任务,outerTask指向的仍然是当前任务,又返回到这个run()方法,这样就完成了定时任务
reExecutePeriodic(outerTask);
}
}
}
setNextRunTime()
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
当period
大于0的时候,下次执行时间就是time
(本次任务执行开始时间)+period
。
小于0时,则是,当前时间(可以理解为本次任务执行结束时间)加上period
的绝对值。
这个方法就解释了fixedRate和fixedDelay的区别了。
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
和之前delayedExecute
方法差不多,先添加任务到workQueue,然后就准备被执行就好了。
经过上面的分析,我们发现ScheduledThreadPoolExecutor
中的任务在核心线程池只有corePoolSize的时候,线程池中也最多只能有这么多个线程,后续进来的任务都会放入workQueue中,DelayedWorkQueue
内部的存储结构为顺序存储(数组),逻辑结构为排序二叉树,排序方式为任务的执行时间,执行时间越早的排在越前面。