Spring @Scheduled線程模型探究 - 源碼追蹤

上一篇我們來分析一下 @Scheduled 這個定時器的註解實現

首先我看下@Scheduled註解的定義

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
	String CRON_DISABLED = "-";
	String cron() default "";
	String zone() default "";
	long fixedDelay() default -1;
	String fixedDelayString() default "";
	long fixedRate() default -1;
	String fixedRateString() default "";
	long initialDelay() default -1;
	String initialDelayString() default "";
}

註解的字段分別對cron表達式、fixedDelay、fixRate進行了支持,可以傳入value

緊接着我們看下@Scheduled在哪裏被用到,跟進去查看一下實現
在這裏插入圖片描述

這裏我們主要看ScheduledAnnotationBeanPostProcessor這個類,其他幾個類我們稍後再說。
在這裏插入圖片描述

ScheduledAnnotationBeanPostProcessor是spring BeanPostProcessor的實現,postProcessAfterInitialization(Object bean, String beanName)方法在Bean初始化之後執行。

  1. 提取定時方法:抽取@Scheduled註解以及被註解的方法
  2. 處理定時方法:對定時方法進行註冊委託給定時器,通過processScheduled()方法實現

processScheduled方法提取了註解上的值,cron、fixedDelay、fixRate分別包裝成不同的對象,通過registrar.scheduleCronTask方法註冊到registrar中

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		 	Runnable runnable = createRunnable(bean, method);
			Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
			
			// 確定定時器初始延遲時間
			long initialDelay = scheduled.initialDelay();
			String initialDelayString = scheduled.initialDelayString();
			...
			initialDelay = parseDelayAsLong(initialDelayString);
			...

			// cron表達式實現
			String cron = scheduled.cron();
			...
			tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
			...

			// fixed delay實現
			long fixedDelay = scheduled.fixedDelay();
			...
			tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
			...

			// fixed rate實現
			long fixedRate = scheduled.fixedRate();
			...
			tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
			...
		}
	}

registrar是ScheduledTaskRegistrar類的實例,ScheduledTaskRegistrar類主要結構如下

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {
	private TaskScheduler taskScheduler;
	private ScheduledExecutorService localExecutor;
	private List<TriggerTask> triggerTasks;
	private List<CronTask> cronTasks;
	private List<IntervalTask> fixedRateTasks;
	private List<IntervalTask> fixedDelayTasks;
	private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);
	private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);
	
	public void setTaskScheduler(TaskScheduler taskScheduler){...}
	public void setScheduler(@Nullable Object scheduler) {...}
	
	public ScheduledTask scheduleCronTask(CronTask task) {...}
	public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {...}
	public ScheduledTask scheduleFixedDelayTask(IntervalTask task) {...}
}

以cron爲例註冊邏輯實現如下,把包裝的Runnable方法和Trigger交由taskScheduler處理
在這裏插入圖片描述

再看下TaskScheduler這裏是個接口,分別提供了三種模式的定義如下

public interface TaskScheduler {
	// ****** cron支持  ******
	ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
	ScheduledFuture<?> schedule(Runnable task, Date startTime);

	// ****** FixedRate支持  ******
	ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period);
	ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period);
	
	// ****** FixedDelay支持  ******
	ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
	ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay);

}

看到這裏我們就大概明白了,上面分析可以總結爲 提取定時方法委託給taskScheduler執行。這個***taskScheduler的實現纔是線程模型的關鍵***。又上邊ScheduledTaskRegistrar類我們可以看到taskScheduler是通過setTaskScheduler、setScheduler方法傳入的,我們只需要找到這倆方法在上面地方被調用即可。
在這裏插入圖片描述在這裏插入圖片描述

這倆方法都是在ScheduledAnnotationBeanPostProcessor#finishRegistration處被調用,finishRegistration在onApplicationEvent()階段被執行
在這裏插入圖片描述
具體執行邏輯如下,注意兩處判斷邏輯,如果ScheduledAnnotationBeanPostProcessor實例的scheduler不爲空就把它設置給ScheduledTaskRegistrar,否則進入下面加載邏。
在這裏插入圖片描述

按照先類型、再名稱順序從容器中分別查找TaskScheduler、ScheduledExecutorService對象
在這裏插入圖片描述

上述邏輯打斷點發現默認通過this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false))方法加載到TaskScheduler實例,通過resolveNameBean得到一個NameBeanHolder實例,我們知道Spring 默認類別名爲類名首字母小寫,這裏傳入類型爲TaskScheduler,holder.gerBeanName()爲taskScheduler
在這裏插入圖片描述
TaskScheduler的實現有如下三種
在這裏插入圖片描述
通過debug發現加載的實例爲ThreadPoolTaskScheduler類型
在這裏插入圖片描述
到這裏我們已經知道了***@Scheduled是如何被裝載處理的*** 、 委託執行對象(定時器執行單元)是如何加載的以及***加載的對象是ThreadPoolTaskScheduler類型***,那爲什麼默認是這個類型呢?別忘了我們還有一個非常重要的註解@EnableScheduling,這個註解是定時任務的開關、並且會進行缺省配置。TaskSchedulingAutoConfiguration爲自動配置類

從配置文件中加載默認配置構造TaskSchedulerBuilder對象並執行構建方法builder進行構建
在這裏插入圖片描述

build()實現
在這裏插入圖片描述
ThreadPoolTaskScheduler默認線程爲1,內部依賴JDK ScheduledExecutorService實現在這裏插入圖片描述 在這裏插入圖片描述
createExecutor()實現,可以看到創建了一個ScheduledExecutorService,默認poolSize爲1
在這裏插入圖片描述

由此分析與驗證結果一致。@Scheduled註解默認採用單線程執行,執行單元是Spring 實現的ThreadPoolTaskScheduler封裝了JDK JUC調度器ScheduledExecutorService實現。

最後再往回翻代碼發現,ScheduledAnnotationBeanPostProcessor#setScheduler(Object)方法的註釋已經說的很清楚,如果不進行配置,默認實現爲單線程。
在這裏插入圖片描述

接下來就是解決問題了,請見下篇《Spring @Scheduled線程模型探究 - 解決問題》。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章