基于springboot实现定时任务

springboot 框架本身的定时任务比较简单,在启动类中使用 @EnableScheduling 注解开启定时任务,会自动扫描,相当于一个开关,把这个开关开完之后,那么只要在相应的任务类中做相应的任务,那么就会被 spring boot 容器扫描到,扫描到后,根据任务定义的时间会自动运行。
先搭建好Spring Boot微服务,加上这个注解 @EnableScheduling :

@SpringBootApplication
@EnableCaching // 启用缓存功能
@EnableScheduling // 开启定时任务功能
public class SpringBootTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootTaskApplication.class, args);
    }
}

@Scheduled参数说明:

  • cron:cron表达式,指定任务在特定时间执行;
  • fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
  • fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
  • fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
  • fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
  • initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
  • initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
  • zone:时区,默认为当前时区,一般没有用到。

Cron表达式参数分别表示:

  • 秒(0~59) 例如0/5表示每5秒
  • 分(0~59)
  • 时(0~23)
  • 日(0~31)的某天,需计算
  • 月(0~11)
  • 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)
在线cron表达式生成:http://qqe2.com/cron/index
1、单线程执行
/**
 * scheduler定时器执行任务的类
 */
@Component
public class PrintTask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private int i;
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

	/**
	* 注解表明这是一个需要定时执行的方法,
	* 里面的cron属性接收的是一个cron表达式,这里的意思是每隔15秒执行一次方法
	*/
    @Scheduled(cron = "*/15 * * * * ?")
    public void execute() {
        logger.info("thread id:{},PrintTask execute times:{}", Thread.currentThread().getId(), ++i);
    }
    /**
     * 每5s执行一次
     */
    @Scheduled(fixedRate = 5000)
    public void taskOne(){
        logger.info("定时任务1执行!!!执行时间:{}",dateFormat.format(new Date()));
    }
}

缺点: 使同一个线程中串行执行,如果只有一个定时任务,这样做肯定没问题,当定时任务增多,如果一个任务卡死,会导致其他任务也无法执行。

2、多线程执行

在SpringBoot项目中使用config配置类的方式添加多线程异步配置,然后在定时任务的类或者方法上添加@Async ,最后重启项目,每一个任务就会在不同的线程中。新建一个AsyncConfig类:

}
@Configuration //表明该类是一个配置类
@EnableAsync //开启异步事件的支持
@RefreshScope //动态刷新配置
@ConfigurationProperties(prefix = "taskthread.config")
public class AsyncConfig {
    private int corePoolSize = 10;
    private int maxPoolSize = 200;
    private int queueCapacity = 10;
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.initialize();
        return executor;
    }
}

任务类加上异步注解:

@Component
public class PrintTask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private int i;
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

	/**
	* 注解表明这是一个需要定时执行的方法,
	* 里面的cron属性接收的是一个cron表达式,这里的意思是每隔15秒执行一次方法
	*/
	@Async
    @Scheduled(cron = "*/15 * * * * ?")
    public void execute() {
        logger.info("thread id:{},PrintTask execute times:{}", Thread.currentThread().getId(), ++i);
    }
    /**
     * 每5s执行一次
     */
    @Async
    @Scheduled(fixedRate = 5000)
    public void taskOne(){
        logger.info("定时任务1执行!!!执行时间:{}",dateFormat.format(new Date()));
    }
}

注意点: 第一个定时任务和第二个定时任务互不影响;并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。

缺点:

  • 1、执行周期写死在代码里了,没有办法动态改变,要想改变只能修改代码在重新部署启动微服务
  • 2、分布式应用中多个实例的定时任务是会同时执行的,这样不仅会消耗资源,而且可能还会引起数据库锁。