前言
在一開始學習定時任務的時候是使用的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
內部的存儲結構爲順序存儲(數組),邏輯結構爲排序二叉樹,排序方式爲任務的執行時間,執行時間越早的排在越前面。