您的当前位置:首页正文

springboot集成schedule实现定时任务

2022-01-31 来源:好走旅游网
springboot集成schedule实现定时任务

背景

在项⽬开发过程中,我们经常需要执⾏具有周期性的任务。通过定时任务可以很好的帮助我们实现。我们拿常⽤的⼏种定时任务框架做⼀个⽐较:

从以上表格可以看出,Spring Schedule框架功能完善,简单易⽤。对于中⼩型项⽬需求,Spring Schedule是完全可以胜任的。

1、springboot集成schedule1.1 添加maven依赖包

由于Spring Schedule包含在spring-boot-starter基础模块中了,所有不需要增加额外的依赖。

org.springframework.boot spring-boot-starter

org.springframework.boot spring-boot-starter-test test

1.2 启动类,添加启动注解

在springboot⼊⼝或者配置类中增加@EnableScheduling注解即可启⽤定时任务。

@EnableScheduling@SpringBootApplication

public class ScheduleApplication { public static void main(String[] args) {

SpringApplication.run(ScheduleApplication.class, args); }}

1.3.添加定时任务

我们将对Spring Schedule三种任务调度器分别举例说明。1.3.1 Cron表达式

类似于Linux下的Cron表达式时间定义规则。Cron表达式由6或7个空格分隔的时间字段组成,如下图:

常⽤表达式:

举个栗⼦:

添加⼀个work()⽅法,每10秒执⾏⼀次。

注意 :当⽅法的执⾏时间超过任务调度频率时,调度器会在下个周期执⾏。

如:假设work()⽅法在第0秒开始执⾏,⽅法执⾏了12秒,那么下⼀次执⾏work()⽅法的时间是第20秒。

@Component

public class MyTask {

@Scheduled(cron = \"0/10 * * * * *\") public void work() { // task execution logic }}

1.3.2 固定间隔任务

下⼀次的任务执⾏时间,是从⽅法最后⼀次任务执⾏结束时间开始计算。并以此规则开始周期性的执⾏任务。举个栗⼦:

添加⼀个work()⽅法,每隔10秒执⾏⼀次。

例如:假设work()⽅法在第0秒开始执⾏,⽅法执⾏了12秒,那么下⼀次执⾏work()⽅法的时间是第22秒。

@Scheduled(fixedDelay = 1000*10)public void work() { // task execution logic}

1.3.3 固定频率任务

按照指定频率执⾏任务,并以此规则开始周期性的执⾏调度。举个栗⼦:

添加⼀个work()⽅法,每10秒执⾏⼀次。

注意 :当⽅法的执⾏时间超过任务调度频率时,调度器会在当前⽅法执⾏完成后⽴即执⾏下次任务。例如:假设work()⽅法在第0秒开始执⾏,⽅法执⾏了12秒,那么下⼀次执⾏work()⽅法的时间是第12秒。

@Scheduled(fixedRate = 1000*10)public void work() { // task execution logic}

2、配置TaskScheduler线程池

在实际项⽬中,我们⼀个系统可能会定义多个定时任务。那么多个定时任务之间是可以相互独⽴且可以并⾏执⾏的。

通过查看org.springframework.scheduling.config.ScheduledTaskRegistrar源代码,发现spring默认会创建⼀个单线程池。这样对于我们的多任务调度可能会是致命的,当多个任务并发(或需要在同⼀时间)执⾏时,任务调度器就会出现时间漂移,任务执⾏时间将不确定。

protected void scheduleTasks() { if (this.taskScheduler == null) {

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

//省略...}

2.1 ⾃定义线程池

新增⼀个配置类,实现SchedulingConfigurer接⼝。重写configureTasks⽅法,通过taskRegistrar设置⾃定义线程池。

@Configuration

public class ScheduleConfig implements SchedulingConfigurer { @Override

public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); }

@Bean(destroyMethod=\"shutdown\") public Executor taskExecutor() {

return Executors.newScheduledThreadPool(20); }}

3、实际应⽤中的问题

3.1 Web应⽤中的启动和关闭问题

我们知道通过spring加载或初始化的Bean,在服务停⽌的时候,spring会⾃动卸载(销毁)。但是由于线程是JVM级别的,如果⽤户在Web应⽤中启动了⼀个线程,那么这个线程的⽣命周期并不会和Web应⽤保持⼀致。也就是说,即使Web应⽤停⽌了,这个线程依然没有结束(死亡)。解决⽅法:

1)当前对象是通过spring初始化

spring在卸载(销毁)实例时,会调⽤实例的destroy⽅法。通过实现DisposableBean接⼝覆盖destroy⽅法实现。在destroy⽅法中主动关闭线程。

@Component

public class MyTask implements DisposableBean{ @Override

public void destroy() throws Exception { //关闭线程或线程池

ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler)applicationContext.getBean(\"scheduler\"); scheduler.shutdown(); }

//省略...}

2)当前对象不是通过spring初始化(管理)

那么我们可以增加⼀个Servlet上下⽂监听器,在Servlet服务停⽌的时候主动关闭线程。

public class MyTaskListenter implements ServletContextListener{ @Override

public void contextDestroyed(ServletContextEvent arg0) { //关闭线程或线程池 }

//省略...}

3.2 分布式部署问题

在实际项⽬中,我们的系统通常会做集群、分布式或灾备部署。那么定时任务就可能出现并发问题,即同⼀个任务在多个服务器上同时在运⾏。解决⽅法(分布式锁):1)通过数据库表锁2)通过缓存中间件3)通过Zookeeper实现总结:

spring schedule给我们提供了⼀套简单、快速、⾼效、稳定的定时任务框架。但需要考虑线程的⽣命周期及分布式部署问题。

以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

因篇幅问题不能全部显示,请点此查看更多更全内容