使用基于 Spring 注解的定时任务调度

工作中难免会有一些定时调度需求,比如定时统计数据,定时清理垃圾等等。你可能用过 Quartz 框架,但是现在基本上已经被淘汰了,因为其使用起来还是有些复杂。目前单机定时任务基本上都使用基于 Spring 注解的定时调度,分布式定时任务基本上都使用 xxl job 定时调度,原因就是使用起来很非常简单。

本篇博客主要介绍基于 Spring 注解的定时任务调度,特点就是使用简单,只需要掌握几个注解的使用就可以了,基本上已经可以满足绝大多数场景的需求。当然为了保证高可用,还是尽量使用 xxl job 分布式定时任务调度,这个后面再介绍。

在本篇博客的最后会提供源代码下载,话不多说,直接上代码介绍。


一、搭建工程

搭建一个 SpringBoot 工程,结构如下:

image

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 秒执行一次任务,最终的结果是这两个任务交替执行,而无法同时执行,如下图:

image

每隔 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 个定时任务,执行效果如下:

image

从配置文件中读取的 cron 表达式是每隔 3 秒执行一次,由于代码中明确要休眠 5 秒,所以 2 秒内方法中的代码肯定是执行不完的,因此方法需要 5 秒才能执行完。但是由于线程都是异步执行的,所以多个定时调度任务之间互不影响,同时执行。

对于同一个任务来说,即使本次调度还没执行完,下次调度时间到了,就会立刻再创建一个新线程执行,因此从上图中可以发现,任务相隔时间就是 3 秒,跟配置的 cron 表达式的间隔时间相同。


OK,以上就是全部内容,基于 Spring 注解的定时任务调度已经介绍完毕。

在实际测试过程中,可以通过注释类上的 @Component 注解进行测试,比如想要测试单线程的运行效果,可以把 MutiThreadTask 类上的 @Component 注解注释掉,启动 SingleThreadTask 类上的 @Component 注解。

本篇博客的源代码地址为:https://files.cnblogs.com/files/blogs/699532/springboot_schedule.zip

posted @ 2023-08-30 23:32  乔京飞  阅读(11639)  评论(0编辑  收藏  举报