定时任务
定时任务使用场景:
- 定时数据备份
- 订单超时自动取消
- 按时间段统计信息
- 定时发送短信或邮件
- … …
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(年)
其中允许的特殊字符包括:
- ,:表示列出枚举值。
- *:表示匹配该域的任意值。
- ?:表示不指定值。
- /:字符用来指定数值的增量。
- 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 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());
}
}
通过执行结果可以看到即使任务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);
}
}
通过执行结果可以看到两个任务随机在设定的5个线程里面执行,做到两个任务互不干扰,从而避免阻塞。