我們都知道再spring 中使用定時任務可以直接在要執行定時任務的方法上面加註解@Scheduled(cron="0/1 * * * * ?")。但是爲什麼只需這簡單的一個註解就能執行定時任務,我們來看源碼一點點分析。在項目中必須還加@EnableScheduling才能真正的啓動定時任務,也就是去註冊執行定時任務。
我們來看@EnableScheduling的定義:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
可以看到實際上是通過@Import註解加載了另外的配置屬性類(SchedulingConfiguration.class):
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
裏面定義了ScheduledAnnotationBeanPostProcessor這個bean。就會在context初始化時候,查找我們代碼中的@Scheduled,並把它們轉換爲定時任務。
可以看到主要就是去創建了ScheduledAnnotationBeanPostProcessor();
ScheduledAnnotationBeanPostProcessor.java源碼分析:
//spring的容器有多個,有的容器是不會調用這個setApplicationContext的
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
if (this.beanFactory == null) {
this.beanFactory = applicationContext;
}
}
Spring在完成這個Bean的初始化後,ScheduledAnnotationBeanPostProcessor類實現了ApplicationContextAware接口,所以直接調用了setApplicationContext方法將ApplicationContext上下文對象注入至該Bean對象(@EnableScheduling)中。
然後在每個bean初始化之後就會去調用postProcessAfterInitialization方法(觀察者模式)去會查找這個bean中任何帶有@Scheduled的方法。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) 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: " + targetClass);
}
}
else {
// Non-empty set of methods
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
找到帶有@Scheduled的方法這個方法之後調用了processScheduled方法。
/**
* Process the given {@code @Scheduled} method declaration on the given bean.
* @param scheduled the @Scheduled annotation
* @param method the method that the annotation has been declared on
* @param bean the target bean instance
* @see #createRunnable(Object, Method)
*/
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<>(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);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
// Check cron expression
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
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 類型是否爲fixedDelay
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(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 FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
processScheduled這個方法實際上就是去註冊任務並去觸發執行任務(實際沒有觸發執行)。
Runnable runnable = createRunnable(bean, method);
這句話就是會去用定時調用目標bean實例和執行方法去生成一個線程實例,看到這裏,實際上就已經知道了定時任務執行的時候最終執行的就是這個線程,spring的@Scheduled底層是用線程來實現的。我們去看一下createRunnable這個方法,首先會檢驗@Scheduled註解的方法的訪問權限,然後再去動態代理去執行該方法:
protected Runnable createRunnable(Object target, Method method) {
Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");
//這句就是爲了檢驗方法的訪問權限不是private
Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
return new ScheduledMethodRunnable(target, invocableMethod);
}
public class ScheduledMethodRunnable implements Runnable {
private final Object target;
private final Method method;
public ScheduledMethodRunnable(Object target, Method method) {
this.target = target;
this.method = method;
}
public ScheduledMethodRunnable(Object target, String methodName) throws NoSuchMethodException {
this.target = target;
this.method = target.getClass().getMethod(methodName);
}
public Object getTarget() {
return this.target;
}
/**
* Return the target method to call.
*/
public Method getMethod() {
return this.method;
}
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(this.method);
//動態代理去執行
this.method.invoke(this.target);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getTargetException());
}
catch (IllegalAccessException ex) {
throw new UndeclaredThrowableException(ex);
}
}
@Override
public String toString() {
return this.method.getDeclaringClass().getName() + "." + this.method.getName();
}
}
生成runable之後,就會判斷三種類型@Scheduled註解的三個屬性fixedRate(fixedRateString), fixedDelay(fixedDelayString), 以及 cron。我們最常用的就是cron表達式。另外兩個的解釋說明:
1、fixedDelay 固定延遲時間,這個週期是以上一個調用任務的完成時間爲基準,在上一個任務完成之後,5s後再次執行。
2、fixedRate 固定速率執行執行,是從上一次方法執行開始的時間算起,如果上一次方法阻塞住了,下一次也是不會執行,但是在阻塞這段時間內累計應該執行的次數,當不再阻塞時,一下子把這些全部執行掉,而後再按照固定速率繼續執行。這個週期是以上一個任務開始時間爲基準,從上一任務開始執行後5s再次調用。
我們就拿cron的來說,其他兩個類型的調用方法差不多,只是類型不同。
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
CronTrigger.class
//Cron表達式來生成調度計劃,Spring對cron表達式的支持,是由CronSequenceGenerator實現的
public class CronTrigger implements Trigger {
private final CronSequenceGenerator sequenceGenerator;
public CronTrigger(String expression) {
this.sequenceGenerator = new CronSequenceGenerator(expression);
}
public CronTrigger(String expression, TimeZone timeZone) {
this.sequenceGenerator = new CronSequenceGenerator(expression, timeZone);
}
public String getExpression() {
return this.sequenceGenerator.getExpression();
}
//獲得下一次任務的執行時間
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date date = triggerContext.lastCompletionTime();
if (date != null) {
Date scheduled = triggerContext.lastScheduledExecutionTime();
if (scheduled != null && date.before(scheduled)) {
// Previous task apparently executed too early...
// Let's simply use the last calculated execution time then,
// in order to prevent accidental re-fires in the same second.
date = scheduled;
}
}
else {
date = new Date();
}
return this.sequenceGenerator.next(date);
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CronTrigger &&
this.sequenceGenerator.equals(((CronTrigger) other).sequenceGenerator)));
}
@Override
public int hashCode() {
return this.sequenceGenerator.hashCode();
}
@Override
public String toString() {
return this.sequenceGenerator.toString();
}
}
ScheduledTaskRegistrar.class(部分關鍵代碼)
public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {
public static final String CRON_DISABLED = "-";
//負責執行任務
//Spring任務調度器的核心接口,定義了執行定時任務的主要方法,主要根據任務的不同觸發方式調用不同的執行邏輯,其實現類都是對JDK原生的定時器或線程池組件進行包裝,並擴展額外的功能。
@Nullable
private TaskScheduler taskScheduler;
//沒有設置taskScheduler就會使用Executors.newSingleThreadScheduledExecutor()
@Nullable
private ScheduledExecutorService localExecutor;
//CronTask類型的任務集合
@Nullable
private List<TriggerTask> triggerTasks;
//FixedRateTask類型的任務集合
@Nullable
private List<CronTask> cronTasks;
//FixedRateTask類型的任務集合
@Nullable
private List<IntervalTask> fixedRateTasks;
//FixedDelayTask類型的任務集合
@Nullable
private List<IntervalTask> fixedDelayTasks;
//還未真正執行的任務
private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);
//已經啓動執行的任務
private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);
public void addCronTask(Runnable task, String expression) {
if (!CRON_DISABLED.equals(expression)) {
addCronTask(new CronTask(task, expression));
}
}
public void addCronTask(CronTask task) {
if (this.cronTasks == null) {
this.cronTasks = new ArrayList<>();
}
this.cronTasks.add(task);
}
@Override
public void afterPropertiesSet() {
scheduleTasks();
}
//spring容器初始化完成最終觸發所有定時任務的方法
@SuppressWarnings("deprecation")
protected void scheduleTasks() {
//這時候如果taskScheduler在之前沒有初始化賦值成功就會在這裏再次進行賦值,保證定時任務能夠正常執行
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
private void addScheduledTask(@Nullable ScheduledTask task) {
if (task != null) {
this.scheduledTasks.add(task);
}
}
@Nullable
public ScheduledTask scheduleCronTask(CronTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
if (this.taskScheduler != null) {
//真正觸發執行了定時任務
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
else {
addCronTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
@Override
public Set<ScheduledTask> getScheduledTasks() {
return Collections.unmodifiableSet(this.scheduledTasks);
}
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks) {
task.cancel();
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
}
}
}
scheduleCronTask方法就是要執行的方法。其中又把task封裝爲了ScheduledTask。其實ScheduledTask這個類裏面就是把task(任務信息)、ScheduledFuture(進行任務得一些操作)放了進去,ScheduledFuture操作:
V get()
: 獲取結果,若無結果會阻塞至異步計算完成V get(long timeOut, TimeUnit unit)
:獲取結果,超時返回nullboolean isDone()
:執行結束(完成/取消/異常)返回trueboolean isCancelled()
:任務完成前被取消返回trueboolean cancel(boolean mayInterruptRunning)
:取消任務,未開始或已完成返回false,參數表示是否中斷執行中的線程
當創建了Future實例,任務可能有以下三種狀態:
- 等待狀態。此時調用
cancel()
方法不管傳入true還是false都會標記爲取消,任務依然保存在任務隊列中,但當輪到此任務運行時會直接跳過。 - 完成狀態。此時
cancel()
不會起任何作用,因爲任務已經完成了。 - 運行中。此時傳入true會中斷正在執行的任務,傳入false則不會中斷。
我們在源碼中可以看到在真正執行定時任務的時候這裏面會判this.taskScheduler 是否爲空,如果爲空的話就不去執行定時任務。那此時的這個this.taskScheduler其實是空的,在這之前根據走過來的源碼還沒有給這個ScheduledTaskRegistrar類中的taskScheduler賦值的,所以就只是把這個創建好的定時任務先放進了unresolvedTasks(還未真正執行的任務)集合中,並沒有真正的去觸發。那到底什麼時候纔會真正的去觸發了呢,我們接着一步步走看一下。接着就再返回到了原來的ScheduledAnnotationBeanPostProcessor類的processScheduled方法。
//儲存任務
private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
......省略
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
......省略
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
......省略
}
可以看到把返回的ScheduledTask方進了Set集合中。然後最終就是把任務放進了scheduledTasks這個map中記錄用於其他地方使用。在創建bean的最後一步就會執行bean的afterSingletonsInstantiated方法,所以定時任務在前面都沒有真正的去觸發,在這最後一步的操作中,纔是真正去觸發執行了定時任務。
//創建bean的最後一步就會執行bean的afterSingletonsInstantiated方法
@Override
public void afterSingletonsInstantiated() {
this.nonAnnotatedClasses.clear();
if (this.applicationContext == null) {
finishRegistration();
}
}
//Spring容器初始化完成後執行
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
finishRegistration();
}
}
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
configurer.configureTasks(this.registrar);
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
//給ScheduledTaskRegistrar中的TaskScheduler賦值,只有給TaskScheduler賦值待會真正的觸發任務
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
logger.trace("Could not find unique TaskScheduler bean", ex);
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
logger.trace("Could not find default TaskScheduler bean", ex);
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
logger.trace("Could not find unique ScheduledExecutorService bean", ex2);
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
logger.trace("Could not find default ScheduledExecutorService bean", ex2);
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
//真正去觸發執行定時任務,最終還是又調用了ScheduledTaskRegistrar中的scheduleCronTask方法
this.registrar.afterPropertiesSet();
}
我們可以看到不管是afterSingletonsInstantiated還是onApplicationEvent最終都調用了finishRegistration方法,而在這個方法中才去把ScheduledTaskRegistrar類中的taskScheduler賦值。
賦值之後纔去真正去觸發執行定時任務,並且最終還是又調用了ScheduledTaskRegistrar中的scheduleCronTask方法。因爲這次調用的時候taskScheduler已經被賦過值了,就會真正執行觸發了定時任務(TaskScheduler.schedule)。
ScheduledTaskRegistrar.java
public ScheduledTask scheduleCronTask(CronTask task) {
*******省略*******
if (this.taskScheduler != null) {
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
*******省略*******
}
接下來就是要深入到TaskScheduler這個定時任務實際執行的線程中裏面的源碼底層是怎麼進行執行這個定時任務的。
TaskScheduler
public interface TaskScheduler {
//調度執行具體任務,定時任務觸發時調用它
ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
default ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
return schedule(task, Date.from(startTime));
}
ScheduledFuture<?> schedule(Runnable task, Date startTime);
default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
return scheduleAtFixedRate(task, Date.from(startTime), period.toMillis());
}
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period);
default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
return scheduleAtFixedRate(task, period.toMillis());
}
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period);
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
return scheduleWithFixedDelay(task, Date.from(startTime), delay.toMillis());
}
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
return scheduleWithFixedDelay(task, delay.toMillis());
}
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay);
}
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
private volatile int poolSize = 1;
private volatile boolean removeOnCancelPolicy = false;
@Nullable
private volatile ErrorHandler errorHandler;
//延遲和定期執行任務的線程池。
@Nullable
private ScheduledExecutorService scheduledExecutor;
// Underlying ScheduledFutureTask to user-level ListenableFuture handle, if any
private final Map<Object, ListenableFuture<?>> listenableFutureMap =
new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
****省略****
//初始化線程池
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);
if (this.removeOnCancelPolicy) {
if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
((ScheduledThreadPoolExecutor) this.scheduledExecutor).setRemoveOnCancelPolicy(true);
}
else {
logger.debug("Could not apply remove-on-cancel policy - not a ScheduledThreadPoolExecutor");
}
}
return this.scheduledExecutor;
}
//創建一個線程池
protected ScheduledExecutorService createExecutor(
int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
}
//返回線程池對象
public ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() throws IllegalStateException {
Assert.state(this.scheduledExecutor instanceof ScheduledThreadPoolExecutor,
"No ScheduledThreadPoolExecutor available");
return (ScheduledThreadPoolExecutor) this.scheduledExecutor;
}
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
//調用ReschedulingRunnable實際執行定時任務
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
****省略****
}
可以看到在ThreadPoolTaskScheduler 裏面主要就是創建了ScheduledExecutorService對象並把它傳遞給下一步操作,而這個就是要執行定時任務的線程池,ScheduledExecutorService在ExecutorService提供的功能之上再增加了延遲和定期執行任務的功能。通過查看源碼最終創建的這個線程池關鍵屬性爲:
corePoolSize:1
maximumPoolSize:Integer.MAX_VALUE
queueSize:16(初始值)
這個任務隊列queue使用的爲專門封裝的DelayedWorkQueue:
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
****省略****
/**
* 動態擴容隊列(會在添加元素的時候offer方法中調用這個方法)
*/
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
}
****省略****
}
ThreadPoolTaskScheduler 接口中的方法和上面TaskScheduler 的接口方法一樣,不過ThreadPoolTaskScheduler 接口是JDK中具體實現的,是在java.util.concurrent包中,而且java沒有提供觸發器可重複觸發的任務,只是提供了可以按照延遲時間執行一次任務。所以spring去封裝了ReschedulingRunnable來實際的去不斷調用java.util.concurrent包中ThreadPoolTaskScheduler 接口去觸發定時任務。
下面我們來看一下spring封裝的ReschedulingRunnable類
//spring定時任務封裝執行類
class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements ScheduledFuture<Object> {
private final Trigger trigger;
/*簡單定時任務數據保存接口(存有定時任務的三個時間變量)
* lastScheduledExecutionTime 上一次任務的執行時間(調度時間)
* lastActualExecutionTime 上一次任務的實際執行開始時間
* lastCompletionTime 上一次任務的實際執行完成時間
*/
private final SimpleTriggerContext triggerContext = new SimpleTriggerContext();
//實際執行任務的線程池
private final ScheduledExecutorService executor;
@Nullable
private ScheduledFuture<?> currentFuture;
@Nullable
private Date scheduledExecutionTime;
private final Object triggerContextMonitor = new Object();
public ReschedulingRunnable(
Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) {
super(delegate, errorHandler);
this.trigger = trigger;
this.executor = executor;
}
@Nullable
public ScheduledFuture<?> schedule() {
/* 這裏加鎖的原因就是雖然默認的線程池實際上poolSize永遠爲1,而且延遲隊列在上面已經說過是無限擴容的
* 所以剛開始給我的感覺就是:既然這樣的話其實在這裏加鎖完全沒有必要的,
* 但是,其實我們在使用定時器可以自定義線程池去執行,所以這時候poolSize就可能不爲1了,
* 所以這時候必須要進行加鎖來防止最終計算出的initialDelay數值錯誤
*/
synchronized (this.triggerContextMonitor) {
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
//這裏交給執行器執行的Runnable對象是this,就是到達執行時間時就回去執行當前類中的run方法。
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}
private ScheduledFuture<?> obtainCurrentFuture() {
Assert.state(this.currentFuture != null, "No scheduled future");
return this.currentFuture;
}
@Override
public void run() {
Date actualExecutionTime = new Date();
//執行被@Scheduler註釋的方法(代理對象)
super.run();
Date completionTime = new Date();
synchronized (this.triggerContextMonitor) {
Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
if (!obtainCurrentFuture().isCancelled()) {
//又去調用schedule()方法,這樣利用遞歸就實現了任務的重複調用。直到任務被取消
schedule();
}
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
synchronized (this.triggerContextMonitor) {
return obtainCurrentFuture().cancel(mayInterruptIfRunning);
}
}
@Override
public boolean isCancelled() {
synchronized (this.triggerContextMonitor) {
return obtainCurrentFuture().isCancelled();
}
}
@Override
public boolean isDone() {
synchronized (this.triggerContextMonitor) {
return obtainCurrentFuture().isDone();
}
}
@Override
public Object get() throws InterruptedException, ExecutionException {
ScheduledFuture<?> curr;
synchronized (this.triggerContextMonitor) {
curr = obtainCurrentFuture();
}
return curr.get();
}
@Override
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
ScheduledFuture<?> curr;
synchronized (this.triggerContextMonitor) {
curr = obtainCurrentFuture();
}
return curr.get(timeout, unit);
}
@Override
public long getDelay(TimeUnit unit) {
ScheduledFuture<?> curr;
synchronized (this.triggerContextMonitor) {
curr = obtainCurrentFuture();
}
return curr.getDelay(unit);
}
@Override
public int compareTo(Delayed other) {
if (this == other) {
return 0;
}
long diff = getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS);
return (diff == 0 ? 0 : ((diff < 0)? -1 : 1));
}
}
ReschedulingRunnable類其實就是spring定時任務的最終時間執行的封裝類,利用遞歸對java的java.util.concurrent包中的ThreadPoolTaskScheduler的schedule方法調用,實現了定時器。去完善了java沒有提供觸發器可重複觸發的任務,只是提供了可以按照延遲時間執行一次任務的更好實現,讓我們方便使用。
看到這裏在這裏其實有一個要注意的地方,那個實際執行任務的線程池的TaskScheduler初始化的corePoolSize爲1,並且queueSize爲爲不斷擴容的無限大。所以在實際執行的時候,所有的@Scheduler註解最終都是使用的這個線程池,並且在ReschedulingRunnable 實際執行的調度方法中(schedule方法)。
@Nullable
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
//executor就是TaskScheduler的
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
****省略****
}
}
所以在實際的執行中,多個定時任務執行時是會出現定時任務執行時間不準確的問題的。只要有一個定時任務(就假設爲A)在執行(阻塞狀態),其他的定時任務必然會等到這個A任務執行完才能夠執行的(而且其他任務會在阻塞的這段時間內應該會執行的任務會直接被丟棄,實際沒有執行,因爲阻塞完成後會計算下一次的運行時間,這時候之前的任務已經無法執行了)。
我寫了一段測試代碼可以證明這種情況:
@Component
@EnableScheduling
public class GeneralScheduled {
private static final Logger logger = LoggerFactory.getLogger(GeneralScheduled.class);
/**
* 定時器 每1分鐘執行一次
*
*/
@Scheduled(cron="0 0/1 * * * ? ")
public void index1() throws Exception {
get_Similarity();
System.out.println("定時任務1-time:"+General.getTime());
}
/**
* 定時器 每5秒執行一次
*
*/
@Scheduled(cron="*/5 * * * * ? ")
public void index2() {
System.out.println("定時任務2-time:"+General.getTime());
}
@Autowired
RatingsService ratingsService;
@Autowired
RecommendBaseGood recommendBaseGood;
@Autowired
MoviesService moviesService;
@Test
public void get_Similarity() {
General.stopwatchBegin();
List<Ratings> ratingsList = ratingsService.getAll();
General.stopwatchEnd();
//耗費時間的操作
List<Integer> goodsId = recommendBaseGood.Get_Similarity(ratingsService.getAll(), 1);
}
}
我們原本希望的結果是每整點分鐘執行一次index1(注意:定時器設置的cron只要是不能被60整除的,即便設置的是每50秒(或者分、小時)執行一次,也不會按照每50秒執行,一般都應設置爲能被60整除的),每5秒執行一次index2.
我們寫定時器是是想讓控制檯理應每整點一分鐘輸出:定時任務1-time,每間隔4秒輸出:定時任務2-time。但是真實的情況不是這樣的,因爲上面說的原因,在任務A比較耗時的操作沒有完成前,控制檯也不會去輸出:定時任務2-time。
控制檯輸出結果:
接下來就是java.util.concurrent包中的ThreadPoolTaskScheduler的schedule方法是怎麼實現按照給定的延遲時間執行一次任務的。
未完待續。。。。。。