标题:玩转SpringBoot之定时任务详解
作者:mmzsblog
原文地址:https://www.mmzsblog.cn/articles/2019/08/08/1565247960802.html
定时向用户推送消息;
定时运算数据;
定时清空缓存;
在 JDK 中,内置了两个类,可以实现定时任务的功能:
java.util.Timer :可以通过创建 java.util.TimerTask 调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在 坑坑 ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样,ScheduledExecutorService 就解决了 Timer 串行的问题。
在日常开发中,我们很少直接使用 Timer 或 ScheduledExecutorService 来实现定时任务的需求。主要有几点原因:
它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要我们结合 Calendar 自行计算,才能实现复杂时间的调度。例如说,每天、每周五、2019-11-11 等等。
它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要等多考虑,多个进程下,同一个任务在相同时刻,不能重复执行。
项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装。
所以,一般情况下,我们会选择专业的调度任务中间件。
使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:
Timer 、Quartz、Spring Boot三者实现定时任务例子:https://blog.csdn.net/fastlearn/article/details/83306796
springboot支持定时任务:
在要定时执行的方法上加个@Scheduled OK了
形如:@Scheduled(cron = “0 0/5 * * * ?”)
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class SaticScheduleTask {
//3.添加定时任务
@Scheduled(cron = "0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
}
}
运行结果
Cron表达式由6~7项组成,中间用空格分开。从左到右依次是: 秒、分、时、日、月、周几、年(可省略)
Cron表达式的值可以是数字,也可以是以下符号:
“*”:所有值都匹配
“?”:无所谓,不关心,通常放在“周几”里
“,”:或者
“/”:增量值
“-”:区间
ps: cron表达式倒着看比较好理解意思;遇到像1月这样的不足两位数的情况,不用补成01
* * * * ?:每分钟(当秒为0的时候)
0 * * * ?:每小时(当秒和分都为0的时候)
0/5 * * * * ?:每5秒
5/15 * * * ?:每小时的5分、20分、35分、50分
0 9,13 * * ?:每天的9点和13点
0 8-10 * * ?:每天的8点、9点、10点
0/30 8-10 * * ?:每天的8点、8点半、9点、9点半、10点
0 9-17 * * MON-FRI :每周一到周五的9点、10点…直到17点(含)
0 0 25 12 ? :每年12月25日圣诞节的0点0分0秒(午夜)
30 10 * * ? 2016:2016年每天的10点半
Cron表达式生成器:https://www.bejson.com/othertools/cron/
1.依赖
org.springframework.boot
spring-boot-starter
2.0.4.RELEASE
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
org.mybatis
mybatis
3.4.5
compile
2.添加数据库记录:
开启本地数据库mysql,随便打开查询窗口,然后执行脚本内容,如下:
DROP DATABASE IF EXISTS `socks`;
CREATE DATABASE `socks`;
USE `SOCKS`;
DROP TABLE IF EXISTS `cron`;
CREATE TABLE `cron` (
`cron_id` varchar(30) NOT NULL PRIMARY KEY,
`cron` varchar(30) NOT NULL
);
INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');
然后在项目中的application.yml 添加数据源:
spring:
datasource:
url: jdbc:mysql://localhost:3306/socks
username: root
password: 123456
3.创建定时器
数据库准备好数据之后,我们编写定时任务,注意这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容。
具体代码如下:
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {
@Mapper
public interface CronMapper {
@Select("select cron from cron limit 1")
public String getCron();
}
@Autowired //注入mapper
@SuppressWarnings("all")
CronMapper cronMapper;
/**
* 执行定时任务.
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//1.添加任务内容(Runnable)
() -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
//2.设置执行周期(Trigger)
triggerContext -> {
//2.1 从数据库获取执行周期
String cron = cronMapper.getCron();
//2.2 合法性校验.
if (StringUtils.isEmpty(cron)) {
// Omitted Code ..
}
//2.3 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
}
基于注解设定多线程定时任务
创建多线程定时任务
//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@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();
}
}
注: 这里的@Async注解很关键
启动测试
启动应用后,查看控制台:
从控制台可以看出,第一个定时任务和第二个定时任务互不影响;
并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。
springboot实现定时任务详解:https://mp.weixin.qq.com/s/jqN4noo5NazckPCehWFgpA