Spring Series---@Scheduled使用深度理解
功能定位
一種實現程式內定時任務的方法。幾種實現方式類比如下:
1) Java自帶的java.util.Timer類,這個類允許你排程一個java.util.TimerTask任務。 最早的時候就是這樣寫定時任務的。
2)用java.util.concurrent.ScheduledExecutorService 來實現定時任務,精確的併發語義控制,推薦
3) 開源的第三方框架: Quartz 或者 elastic-job , 但是這個比較複雜和重量級,適用於分散式場景下的定時任務,可以根據需要多例項部署定時任務。
4) 使用Spring提供的註解: @Schedule 如果定時任務執行時間較短,並且比較單一,可以使用這個註解。
5)建立一個thread,然後讓它在while迴圈裡一直執行著, 通過sleep方法來達到定時任務的效果。這樣可以快速簡單的實現,但是因為執行緒排程受系統和執行緒競爭影響,無法實現可靠精確定時。
使用方法
實現上主要通過cron和fixedRate兩種方法來實現控制;使用到@Scheduled 和 @EnableScheduled等註解
普通單執行緒
/**
* Created by Administrator on 2018/5/13.
*/
@SpringBootApplication
@EnableScheduling /**需要新增生命使用定時*/
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
logger.info("start");
}
}
/**
* Created by Administrator on 2018/5/13.
*/
@Component
public class ScheduledSingle {
private static final Logger logger = LoggerFactory.getLogger(ScheduledSingle.class);
@Scheduled(cron="0/10 * * * * ?")/**間隔10s執行任務*/
public void executeFileDownLoadTask() {
Thread current = Thread.currentThread();
logger.info("Cron任務:"+current.getId()+ ",name:"+current.getName());
}
@Scheduled(fixedRate = 1000*5, initialDelay = 1000)
public void reportCurrentTime(){
Thread current = Thread.currentThread();
logger.info("fixedRate任務:"+current.getId()+ ",name:"+current.getName());
}
}
執行結果
可以看到儘管是兩個任務但仍然由一個執行緒來執行
併發多執行緒
當定時任務很多的時候,為了提高任務執行效率,可以採用並行方式執行定時任務,任務之間互不影響,
只要實現SchedulingConfigurer介面就可以。
package com.feeler.universe.schedule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2018/5/13.
*/
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(5);
}
}
執行結果
並行執行的時候,建立執行緒池採用了newScheduledThreadPool這個執行緒池。 Executors框架中存在幾種執行緒池的建立,一種是 newCachedThreadPool() ,一種是 newFixedThreadPool(), 一種是 newSingleThreadExecutor()
其中newScheduledThreadPool() 執行緒池的採用的佇列是延遲佇列。newScheduledThreadPool() 執行緒池的特性是定時任務能夠定時或者週期性的執行任務。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
其中執行緒池核心執行緒數是自己設定的,最大執行緒數是最大值。阻塞佇列是自定義的延遲佇列:DelayedWorkQueue()非同步執行
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext rootContext =
new AnnotationConfigApplicationContext();
rootContext.register(RootContextConfiguration.class);
rootContext.refresh();
}
}
@Configuration
@EnableScheduling
@EnableAsync(
mode = AdviceMode.PROXY, proxyTargetClass = false,
order = Ordered.HIGHEST_PRECEDENCE
)
@ComponentScan(
basePackages = "hello"
)
public class RootContextConfiguration implements
AsyncConfigurer, SchedulingConfigurer {
@Bean
public ThreadPoolTaskScheduler taskScheduler()
{
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(20);
scheduler.setThreadNamePrefix("task-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
@Override
public Executor getAsyncExecutor()
{
Executor executor = this.taskScheduler();
return executor;
}
@Override
public void configureTasks(ScheduledTaskRegistrar registrar)
{
TaskScheduler scheduler = this.taskScheduler();
registrar.setTaskScheduler(scheduler);
}
}
執行原理
spring在初始化bean後,通過“postProcessAfterInitialization”攔截到所有的用到“@Scheduled”註解的方法,並解析相應的的註解引數,放入“定時任務列表”等待後續處理;之後再“定時任務列表”中統一執行相應的定時任務(任務為順序執行,先執行cron,之後再執行fixedRate)。重要步驟。
第一步:依次載入所有的實現Scheduled註解的類方法。第一步:依次載入所有的實現Scheduled註解的類方法。
//說明:ScheduledAnnotationBeanPostProcessor繼承BeanPostProcessor。
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
//省略多個判斷條件程式碼
for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
processScheduled(scheduled, method, bean);
}
}
}
return bean;
}
第二步:將對應型別的定時器放入相應的“定時任務列表”中。
//說明:ScheduledAnnotationBeanPostProcessor繼承BeanPostProcessor。
//獲取scheduled類引數,之後根據引數型別、相應的延時時間、對應的時區放入不同的任務列表中
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
//獲取corn型別
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
String zone = scheduled.zone();
//放入cron任務列表中(不執行)
this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
}
//執行頻率型別(long型別)
long fixedRate = scheduled.fixedRate();
String fixedDelayString = scheduled.fixedDelayString();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
//放入FixedRate任務列表中(不執行)(registrar為ScheduledTaskRegistrar)
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
}
//執行頻率型別(字串型別,不接收引數計算如:600*20)
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
fixedRate = Long.parseLong(fixedRateString);
//放入FixedRate任務列表中(不執行)
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
}
}
return bean;
}
第三步:執行相應的定時任務。說明:定時任務先執行corn,判斷定時任務的執行時間,計算出相應的下次執行時間,放入執行緒中,到相應的時間後進行執行。之後執行按“頻率”(fixedRate)執行的定時任務,直到所有任務執行結束。
protected void scheduleTasks() {
//順序執行相應的Cron
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
this.scheduledFutures.add(this.taskScheduler.schedule(
task.getRunnable(), task.getTrigger()));
}
}
//順序執行所有的“fixedRate”定時任務(無延遲,也就是說initialDelay引數為空),因為無延遲,所以定時任務會直接執行一次,執行任務完成後,會將下次執行任務的時間放入delayedExecute中等待下次執行。
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(now + task.getInitialDelay());
this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
task.getRunnable(), startTime, task.getInterval()));
}
else {
this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
task.getRunnable(), task.getInterval()));
}
}
}
//順序執行所有的“fixedRate”定時任務(有延遲,也就是說initialDelay引數不為空)
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(now + task.getInitialDelay());
this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
task.getRunnable(), startTime, task.getInterval()));
}
else {
this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
task.getRunnable(), task.getInterval()));
}
}
}
}
接下來看下定時任務run(extends自Runnable介面)方法:
//說明:每次執行定時任務結束後,會先設定下下次定時任務的執行時間,以此來確認下次任務的執行時間。
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
注意事項
從上面的程式碼可以看出,如果多個定時任務定義的是同一個時間,那麼也是順序執行的,會根據程式載入Scheduled方法的先後來執行。
但是如果某個定時任務執行未完成會出現什麼現象呢?
答:此任務一直無法執行完成,無法設定下次任務執行時間,之後會導致此任務後面的所有定時任務無法繼續執行,也就會出現所有的定時任務“失效”現象。
所以應用springBoot中定時任務的方法中,一定不要出現“死迴圈”、“http持續等待無響應”現象,否則會導致定時任務程式無法正常。再就是非特殊需求情況下可以把定時任務“分散”下。
相關文章
- Spring關於druid使用注入的深度理解SpringUI
- spring使用註解注入bean理解SpringBean
- 深度理解 React SuspenseReact
- 深度理解 Virtual DOM
- Spring5原始碼深度分析(二)之理解@Conditional,@Import註解Spring原始碼Import
- JavaScript深度理解——作用域JavaScript
- Java集合的深度理解Java
- Spring5原始碼深度解析(一)之理解Configuration註解Spring原始碼
- bind/call/apply 深度理解APP
- 深度理解Python迭代器Python
- React事件傳參深度理解React事件
- 深入理解深度學習深度學習
- 深度理解DOM拷貝clone()
- 深度學習淺層理解深度學習
- 理解Spring(一):Spring 與 IoCSpring
- 深度理解Oracle10g中UNDO_RETENTION引數的使用Oracle
- Spring AOP概念理解Spring
- Spring IOC之神理解Spring
- 理解spring (轉載)Spring
- 讓 PM 全面理解深度學習深度學習
- 【vue原始碼】深度理解v-forVue原始碼
- springboot整合schedule(深度理解)Spring Boot
- 原始碼級深度理解 Java SPI原始碼Java
- [Spring 深度解析]第5章 Spring之DAOSpring
- 深度理解Oracle10g中UNDO_RETENTION引數的使用(轉)Oracle
- spring ioc原理解析Spring
- 輕鬆理解 Spring AOPSpring
- Spring Session原理解析SpringSession
- Spring Aop簡單理解Spring
- 深度理解C# 的執行原理C#
- 遞迴 & 分治演算法深度理解遞迴演算法
- 深度理解glibc記憶體分配記憶體
- Spring AOP 原理原始碼深度剖析Spring原始碼
- 深度剖析Spring Cloud底層原理SpringCloud
- 如何讓 Bean 深度感知 Spring 容器BeanSpring
- 如何提升自己對問題的理解深度?
- 學生資訊管理系統之深度理解
- 理解Transformer [資料探勘深度學習]ORM深度學習