SpringBoot中@Scheduled执行原理解析

一:前言

本文主要介绍Spring Boot中使用定时任务的执行原理。

二:@Scheduled使用方式

定时任务注解为@Scheduled。使用方式举例如下:

//定义一个按时间执行的定时任务,在每天16:00执行一次。

@Scheduled(cron = "0 0 16 * * ?")

public void depositJob() {

  //执行代码

}

//定义一个按一定频率执行的定时任务,每隔1分钟执行一次

    @Scheduled(fixedRate = 1000 * 60)

    public void job2() {

    //执行代码

}

//定义一个按一定频率执行的定时任务,每隔1分钟执行一次,延迟1秒执行

    @Scheduled(fixedRate = 1000 * 60,initialDelay = 1000)

    public void updatePayRecords() {

    //执行代码

}

备注:具体参数可以参考“spring-context-4.2.4.RELEASE.jar”下面的“org.springframework.scheduling.annotation.Scheduled"类。

三:@Scheduled代码执行原理说明

简要介绍:spring在初始化bean后,通过“postProcessAfterInitialization”拦截到所有的用到“@Scheduled”注解的方法,并解析相应的的注解参数,放入“定时任务列表”等待后续处理;之后再“定时任务列表”中统一执行相应的定时任务(任务为顺序执行,先执行cron,之后再执行fixedRate)。

重要代码如下:

第一步:依次加载所有的实现Scheduled注解的类方法。

//说明:ScheduledAnnotationBeanPostProcessor继承BeanPostProcessor。

@Override

public Object postProcessAfterInitialization(final Object bean, String beanName) {

          //省略多个判断条件代码

         for (Map.Entry> 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()));

         }

      }

   }

}

接下来看下定时任务runextendsRunnable接口)方法:

//说明:每次执行定时任务结束后,会先设置下下次定时任务的执行时间,以此来确认下次任务的执行时间。

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);

    }

}

备注1:从上面的代码可以看出,如果多个定时任务定义的是同一个时间,那么也是顺序执行的,会根据程序加载Scheduled方法的先后来执行。

但是如果某个定时任务执行未完成会出现什么现象呢? 

答:此任务一直无法执行完成,无法设置下次任务执行时间,之后会导致此任务后面的所有定时任务无法继续执行,也就会出现所有的定时任务失效现象。

所以应用springBoot中定时任务的方法中,一定不要出现死循环“http持续等待无响应现象,否则会导致定时任务程序无法正常。再就是非特殊需求情况下可以把定时任务分散下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 参考springBoot中@Scheduled执行原理解析 spring在初始化bean后,通过“postProc...
    谁在烽烟彼岸阅读 822评论 0 0
  • 参考springBoot中@Scheduled执行原理解析 spring在初始化bean后,通过“postProc...
    huoyl0410阅读 768评论 1 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 今天的大事记,关键词:坚持 成就感 思想。最最最重要的就是,坚持,这又是一个新的开始,我很想大学四年可以坚持一件事...
    刀井阅读 142评论 1 2
  • 我忘了我的名字 你会再一次摩挲我通红的眼眸吗 黎明的泪水 是为夜梦里那轻颦浅笑的消失吗 灼热又冰凉的触感 记忆埋在...
    何处流泪阅读 303评论 0 1