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> 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 Series---@Scheduled使用深度理解)