定时任务

定时任务使用场景:

  • 定时数据备份
  • 订单超时自动取消
  • 按时间段统计信息
  • 定时发送短信或邮件
  • … …

Spring定时任务框架

在Spring框架中实现定时任务的办法至少有两种(不包括Java原生的Timer及Executor实现方式),一种是集成第三方开源定时任务框架Quartz;另一种便是Spring自带的定时器(仅针对3.0之后的版本)。

  • Quartz:这是一个功能比较强大的的三方任务框架,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
  • Spring3.0以后自带的task工具,即:Spring Schedule,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

当然这里要介绍的是Spring Schedule,它的特点有:

  • 基于注解来设置任务,上手非常简单方便。
  • 对代码不具有入侵性,非常轻量级。
  • 默认单线程同步执行。
  • 不支持持久化。

使用Spring Schedule

主要Spring Schedule注解:

  • @EnableScheduling启动定时器
  • @Scheduled设置执行时间,下面是他常用的属性:
  • Cron 根据表达式执行
  • FixedRate 每隔多少时间启动任务,不管任务是否完成
  • FixedDelay 每次执行任务完成之后间隔多久再次执行
  • @EnableAsync开启异步支持
  • @Async标注在方法上,可以使该方法异步的调用执行

Cron表达式

一般在注解@Scheduled里面设置属性时间,通常用Cron表达式设置时间,这样比较灵活方便。

  • Cron语法格式,共7个部分(域)组成(一般只用六个域,个人觉得最后一个年周期性太长,用的比较少):
  • Seconds(秒) Minutes(分钟) Hours(小时) DayofMonth(天/几号) Month(月) DayofWeek(星期几) Year(年)

spring的定时任务会重复执行吗 spring scheduled定时任务_spring

其中允许的特殊字符包括:

  • ,:表示列出枚举值。
  • *:表示匹配该域的任意值。
  • ?:表示不指定值。
  • /:字符用来指定数值的增量。
  • L:表示最后,只能出现在DayofWeek和DayofMonth域。
  • W: 表示有效工作日(周一到周五),只能出现在DayofMonth域。
  • #:用于确定每个月第几个星期几,只能出现在DayofMonth域。

如果嫌太麻烦可以搜Cron表达式生成器:自己提供的Cron表达式生成器

Spring Schedule任务简单实现

基于注解来设置单个任务,springboot上手非常简单方便。只需要两个基本注解就能完成。

@SpringBootApplication
@EnableScheduling
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}
@Slf4j
@Component
public class TestSchedule {
	//每一秒执行一次
    @Scheduled(cron = "0/1 * * * * * ")
    public void test() throws InterruptedException {
    	//打印日志信息
        log.info("test "+Thread.currentThread());
    }
}

spring的定时任务会重复执行吗 spring scheduled定时任务_spring_02

阻止单线程同步造成的任务阻塞

之前提到Spring Schedule默认任务是单线程同步执行,这样多个任务很有可能会造成阻塞。(好比当前有两个任务,第一个任务在执行自己的任务很久了还在执行,那么下个任务需要等待这个任务执行完成后才能够执行)
一般阻塞可以通过同步变成异步或者单线程变多线程来阻止发生。

  • 异步
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}
@Slf4j
@Component
public class TestOne {
    
    @Scheduled(cron = "0/1 * * * * * ")
    @Async
    public void test1() throws InterruptedException {
        log.info("test1 " + Thread.currentThread());
        Thread.sleep(1000 * 5);
    }

    @Scheduled(cron = "0/1 * * * * * ")
    @Async
    public void test2() {
        log.info("test2 " + Thread.currentThread());
    }
}

spring的定时任务会重复执行吗 spring scheduled定时任务_定时任务_03

通过执行结果可以看到即使任务test1休眠5秒,任务test2不会等待任务test1休眠后执行,而是直接执行自己的任务,这就是异步执行。

  • 多线程
    在异步基础上删除异步相关注解,增加ScheduledConfig 配置类。
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(setTaskExecutors());
    }

    @Bean
    public Executor setTaskExecutors(){
    	//线程池创建5个线程
        return Executors.newScheduledThreadPool(5);
    }
}

spring的定时任务会重复执行吗 spring scheduled定时任务_spring_04


通过执行结果可以看到两个任务随机在设定的5个线程里面执行,做到两个任务互不干扰,从而避免阻塞。