在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量。在springboot中可以有很多方案去帮我们完成定时器的工作,有Java自带的java.util.Timer类,也有强大的调度器Quartz,还有SpringBoot自带的Scheduled,今天主要说说Scheduled。
一、定时器比较
框架名称 | Cron表达式 | 固定间隔执行 | 固定频率执行 | 任务持久化 | 难易度 |
---|---|---|---|---|---|
TimerTask |
不支持 | 支持 | 支持 | 不支持 | 一般 |
schedule |
支持 | 支持 | 支持 | 不支持 | 简单 |
Quartz |
支持 | 支持 | 支持 | 支持 | 难 |
在实际应用中,如果没有分布式场景(quartz 支持分布式, schedule 不支持(需要自己实现,用分布式锁,哪个拿到了这把锁,哪个就行执行),schedule跟spring结合的更好,还是很适用的。
二、SpringBoot的定时任务
使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:
- 基于注解
- 基于SchedulingConfigurer接口,我们可以从数据库中读取指定时间来动态执行定时任务
- 基于注解设定多线程定时任务
1. 基于注解@Scheduled
注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。即多个任务都是在同个线程中先后执行。
@Configuration //标识为配置类 @EnableScheduling // 开启定时任务 public class ScheduleTask { //3.添加定时任务 @Scheduled(cron = "0/5 * * * * ?") //或直接指定时间间隔,例如:5秒 //@Scheduled(fixedRate=5000) private void configureTasks() { System.err.println("执行静态定时任务时间: " + LocalDateTime.now()); } }
@Scheduled除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。
cron表达式详解:https://www.cnblogs.com/myitnews/p/11863041.html
2. 基于SchedulingConfigurer接口
@Schedule注解的一个缺点就是其定时时间不能动态更改,它适用于具有固定任务周期的任务,若要修改任务执行周期,只能走“停服务→修改任务执行周期→重启服务”这条路。而基于 SchedulingConfigurer 接口方式可以做到。SchedulingConfigurer 接口可以实现在@Configuration 类上,同时不要忘了,还需要@EnableScheduling 注解的支持。
接口源码:
@FunctionalInterface public interface SchedulingConfigurer { void configureTasks(ScheduledTaskRegistrar var1); }
ScheduledTaskRegistrar类包括以下几个重要方法:
- void addTriggerTask(Runnable task, Trigger trigger)
- void addTriggerTask(TriggerTask task)
- void addCronTask(Runnable task, String expression)
- void addCronTask(CronTask task)
- void addFixedRateTask(Runnable task, long interval)
- void addFixedRateTask(IntervalTask task)
- void addFixedDelayTask(Runnable task, long delay)
- void addFixedDelayTask(IntervalTask task)
@Component @EnableScheduling public class TestTask implements SchedulingConfigurer { // 执行定时任务时间(0 0 2 * * ? 表示上午2点,0 30 9 * * ? 表示上午九点30分) private String cron = "0/10 * * * * ?"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(new Runnable() { @Override public void run() { // 定时任务要执行的内容 System.out.println("【开始执行定时任务。。。】"); } }, new CronTrigger(cron) { @Override public Date nextExecutionTime(TriggerContext triggerContext) { // 定时任务触发,可修改定时任务的执行周期 CronTrigger trigger = new CronTrigger(cron); Date nextExecDate = trigger.nextExecutionTime(triggerContext); return nextExecDate; } }); } }
注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。
3. 多线程定时任务
多线程定时任务是基于@Async注解的。
@Component @EnableScheduling // 1.开启定时任务 @EnableAsync // 2.开启多线程 public class MultithreadScheduleTask { @Async @Scheduled(fixedDelay = 1000) //间隔1秒 public void first() throws InterruptedException { System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); Thread.sleep(1000 * 10); } @Async @Scheduled(fixedDelay = 2000) public void second() { System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); } }
第一个定时任务和第二个定时任务互不影响;由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。