任务调度(1)-Spring @Scheduled详解

Spring3.0版本之后提供了@EnableScheduling和@Scheduled来进行定时任务的功能。

使用Spring创建定时任务非常简单,我们可以使用如下两种方式(当然不仅仅只包括这两种):

  1. 基于注解(@Scheduled)
  2. 基于接口(SchedulingConfigurer),这里可以自定义线程池的线程数和动态设置定时任务的属性

1. 基于注解的实现如下:

首先在Spring启动类添加注解@EnableScheduling

@SpringBootApplication
@EnableScheduling
public class SpringMain {
    public static void main(String[] args) {
        // ...
    }
}

下面是@EnableScheduling的源码(省略注释):

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

其次在我们需要实现任务调度的地方添加@Scheduled注解

@Component
public class Jobs {
    
    @Scheduled(fixedRate = 1000)
    public void fixedRateJob() {
        // ...
    }
    
    
    @Scheduled(fixedDelay = 1000)
    public void fixedDelayJob() {
        // ...
    }
    
    
    @Scheduled(initialDelay = 1000)
    public void initDelayJob() {
        // ...
    }
    
    
    @Scheduled(cron = "0/1 * * * * ? ")
    public void cronJob() {
        // ...
    }
    
    @Scheduled(fixedDelayString = "")
    public void fixedDelayStringJob() {
        // ...
    }

    @Scheduled(fixedRateString = "")
    public void fixedRateStringJob() {
        // ...
    }

    @Scheduled(initialDelayString = "")
    public void initDelayStringJob() {
        // ...
    }

    @Scheduled(zone = "")
    public void zoneJob() {
        // ...
    }
}

下面是@Scheduled的源码(省略注释,添加解释):

   
   // cron表达式
   // 可以去 [Cron - 在线Cron表达式生成器 (ciding.cc)](http://cron.ciding.cc/) 查看
   String cron() default "";
   
   // 时区,一般留空
   String zone() default "";
   
   // 上一次执行完毕时间点之后多长时间再执行,单位是毫秒
   long fixedDelay() default -1;

   // 与fixedDelay一样,只是使用了字符串的形式,可以支持占位符
   String fixedDelayString() default "";

   // 上一次开始执行时间点之后多长时间再执行,单位是毫秒
   long fixedRate() default -1;

   // 与fixedRate一样,只是使用字符串的形式,可以支持占位符
   String fixedRateString() default "";

   // 第一次延迟多长时间后再执行
   long initialDelay() default -1;

    // 与initialDelay一样,只是使用字符串的形式,可以支持占位符
   String initialDelayString() default "";

2. 基于接口(SchedulingConfigurer),这里可以自定义线程池的线程数和动态设置定时任务的属性

为什么基于接口去实现,因为通过查看Spring的源码,发现Spring提供的任务调度是创建了只有一个线程数的线程池。

具体代码如下:

    if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }

这样就会带来一些问题,比如假设我们有个调度任务A,每次执行的时间间隔是5分钟,但是这个调度任务A执行的时间会大于5分钟,因为只有一个线程在执行任务,那么本来5分钟之后应该再次执行的任务会产生延迟,会产生任务无法执行的错觉。

所以我们这里可以通过实现Spring提供的SchedulingConfigurer接口来设置:

  1. 自定义线程池
  2. 动态添加任务和修改任务属性

代码如下:

    @Configuration
    public class DynamicScheduleConfigurer implements SchedulingConfigurer
    {
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
        {
            // 1. 自定义Scheduler
            // ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
            // taskScheduler.setPoolSize(50);
            // taskRegistrar.setTaskScheduler(taskScheduler);
            
            // 2. 添加任务,修改属性
            taskRegistrar.addTriggerTask(
                    // 添加定时任务
                    () -> System.out.println("xxx"),
                    // 设置执行周期(Trigger)
                    triggerContext -> {
                        String cron = "0/5 * * * * ? *";
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    }
            );
        }
}

通过实现这个方法,我们可以将任务的信息放在数据库当中,当程序启动的时候读取数据库任务的信息进行任务的添加,运行时更改任务的信息。

上一篇:做项目管理工具的Basecamp是怎么管理自己的?


下一篇:定时执行任务