使用基于 Spring 注解的定时任务调度
工作中难免会有一些定时调度需求,比如定时统计数据,定时清理垃圾等等。你可能用过 Quartz 框架,但是现在基本上已经被淘汰了,因为其使用起来还是有些复杂。目前单机定时任务基本上都使用基于 Spring 注解的定时调度,分布式定时任务基本上都使用 xxl job 定时调度,原因就是使用起来很非常简单。
本篇博客主要介绍基于 Spring 注解的定时任务调度,特点就是使用简单,只需要掌握几个注解的使用就可以了,基本上已经可以满足绝大多数场景的需求。当然为了保证高可用,还是尽量使用 xxl job 分布式定时任务调度,这个后面再介绍。
在本篇博客的最后会提供源代码下载,话不多说,直接上代码介绍。
一、搭建工程
搭建一个 SpringBoot 工程,结构如下:
SingleThreadTask 类主要演示单线程定时调度,同一时刻只能执行一个任务。
MutiThreadTask 类主要演示多现场定时调度,各个任务互不影响,即使是同一时刻,也可以有多个任务执行。
工程的 pom 文件如下,除了引入 SpringBoot 的最小起步依赖之外,就没再引入别的 jar 包依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jobs</groupId>
<artifactId>springboot_schedule</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
</parent>
<dependencies>
<!--引入最基本的 springboot 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
项目工程的 application.yml 配置文件内容如下:(内容是自己随便定义的配置,配置 cron 表达式)
# 自己随便定义的 cron 表达式
myschedule:
# 每隔 2 秒执行一次
cron1: 0/2 * * * * ?
# 每隔 3 秒执行一次
cron2: 0/3 * * * * ?
在 SpringBoot 的启动类上需要添加注解,具体代码细节如下:
- 为了能够使定时任务生效,需要添加 @EnableScheduling 注解
- 为了能够使异步执行生效,需要添加 @EnableAsync 注解(异步执行是为了在相同时刻,同时执行多个任务)
package com.jobs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
//此注解启用异步线程调用
@EnableAsync
//此注解启用定时任务
@EnableScheduling
@SpringBootApplication
public class ScheduleApp {
public static void main(String[] args) {
SpringApplication.run(ScheduleApp.class, args);
}
}
二、单线程定时任务
代码都在 SingleThreadTask 类中,具体内容如下:
package com.jobs.schedule;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class SingleThreadTask {
/**
* 特点:单线程调度,前面的任务没有执行完,即使到了下一次调度时间,也不会执行新任务
* 不管是在同一个类,还是在不同的类之间,同时配置了多个单线程调度任务,
* 同一时间无法同时执行所配置的多个任务。
* 对于同一个任务来说,上次调度没执行完,下次调度时刻即使到了,也被直接忽略。
*/
//每隔 2 秒调用一次,在代码中可以写死
//@Scheduled(cron = "0/2 * * * * ?")
//从配置文件中,读取所配置的 cron 表达式
@Scheduled(cron = "${myschedule.cron1}")
public void firstTask() {
//休眠 5 秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第 1 个SingleThreadTask任务调度时间:" + LocalDateTime.now());
}
/*
//每隔 2 秒调用一次,在代码中可以写死
//@Scheduled(cron = "0/2 * * * * ?")
//从配置文件中,读取所配置的 cron 表达式
@Scheduled(cron = "${myschedule.cron1}")
public void secondTask() {
//休眠 5 秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第 2 个SingleThreadTask任务调度时间:" + LocalDateTime.now());
}
*/
}
所谓单线程调度,特点就是:无论你配置多少个任务(在单个类中配置任务或者在多个类中配置任务),同一时刻只能运行一个任务。另外对于同一任务来说:前一次调度没执行完,下一次调度即使到了要执行的时间点,也不会执行,并且被直接忽略。
比如上面的代码,你把下面的代码注释取消,这样就可以执行两个任务了,由于我们配置的 cron 表达式相同,从配置文件中读取 myschedule.cron1 的配置内容,也就是每隔 2 秒执行一次任务,最终的结果是这两个任务交替执行,而无法同时执行,如下图:
每隔 2 秒执行一次任务,由于代码中明确要休眠 5 秒,所以 2 秒内方法中的代码肯定是执行不完的,因此方法需要 5 秒才能执行完。从上图中可以发现,两个任务执行完的时间间隔也是 5 秒。由于是单线程调度 2 个任务,因此他们交替执行,而不是并行执行。
三、多线程定时任务
代码都在 MutiThreadTask 类中,具体内容如下:
只需要在方法中增加一个注解 @Async 就可以了,该注解表示方法异步执行,也就是新创建一个新线程中执行
package com.jobs.schedule;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MutiThreadTask {
/**
* 特点:在方法上添加 @Async 注解,表示该方法新开线程执行,也就是异步执行
* 当然如果想让 @Async 注解生效,必须在程序入口的启动类上添加 @EnableAsync 注解
* 这样无论在相同类中,还是在不同的类之间,配置了多个定时任务,到了时间点就会自动执行
*/
//该注解表示该方法异步执行
@Async
//每隔 3 秒调用一次
//@Scheduled(cron = "0/3 * * * * ?")
@Scheduled(cron = "${myschedule.cron2}")
public void firstTask() {
//休眠 5 秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第 1 个MutiThreadTask任务调度时间:" + LocalDateTime.now());
}
/*
//该注解表示该方法异步执行
@Async
//每隔 3 秒调用一次
//@Scheduled(cron = "0/3 * * * * ?")
@Scheduled(cron = "${myschedule.cron2}")
public void secondTask() {
//休眠 5 秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第 2 个MutiThreadTask任务调度时间:" + LocalDateTime.now());
}
*/
}
由于在方法上新增了 @Async 注解,因此每次调度时,方法都会新创建一个线程去执行,因此即使配置了多个定时任务,互相不会受到任何影响,也就是说在同一时刻可以执行多个定时调度任务。可以把上面代码中的注释取消掉,这样就配置了 2 个定时任务,执行效果如下:
从配置文件中读取的 cron 表达式是每隔 3 秒执行一次,由于代码中明确要休眠 5 秒,所以 2 秒内方法中的代码肯定是执行不完的,因此方法需要 5 秒才能执行完。但是由于线程都是异步执行的,所以多个定时调度任务之间互不影响,同时执行。
对于同一个任务来说,即使本次调度还没执行完,下次调度时间到了,就会立刻再创建一个新线程执行,因此从上图中可以发现,任务相隔时间就是 3 秒,跟配置的 cron 表达式的间隔时间相同。
OK,以上就是全部内容,基于 Spring 注解的定时任务调度已经介绍完毕。
在实际测试过程中,可以通过注释类上的 @Component 注解进行测试,比如想要测试单线程的运行效果,可以把 MutiThreadTask 类上的 @Component 注解注释掉,启动 SingleThreadTask 类上的 @Component 注解。
本篇博客的源代码地址为:https://files.cnblogs.com/files/blogs/699532/springboot_schedule.zip