Java定时任务——Spring Task

基本概念

Spring Task 主要是用来实现定时任务的,定时任务的业务场景非常常见,比如:

① 数据定时备份
② 视频网站定时发布视频
③ 系统定时生成数据报表

我们可以直接使用 Spring 提供的 @Scheduled 注解即可定义定时任务,默认情况下,@Scheduled 的任务都在 Spring 创建的大小为 1 的默认线程池中执行(ScheduledThreadPoolExecutor)。该注解常用的几个参数如下:

1.cron

使用 Cron 表达式定义任务的执行时间。Cron 表达式是一种用于调度任务的字符串,通常用于定义定时任务的执行时间。它由六或七个字段组成,每个字段代表一个时间单位,依次为秒、分钟、小时、日期、月份、星期几和(可选的)年份。各字段通过空格分隔

在线 Cron 表达式生成器:http://cron.qqe2.com/

每一个字段的有效范围如下:

秒(Seconds):0-59
分钟(Minutes):0-59
小时(Hours):0-23
日期(Day of Month):1-31
月份(Month):1-12 或 JAN-DEC
星期几(Day of Week):0-7(0、7 表示星期天,1 表示星期二,以此类推)或 SUN-SAT
年份(Year,可选):1970-2099

特殊字符:

*:代表所有可能的值。例如,在秒字段为 * 表示每一秒触发
,:用于指定多个值。例如,在分钟字段中 5,10 表示第 5 和第 10 分钟
-:用于指定一个范围的值。例如,在小时字段中 1-5 表示从 1 点到 5 点每小时触发一次
/:表示起始时间开始触发,然后每隔固定时间触发一次。例如,在秒字段中 0/15 表示起始时间 0s 触发一次,之后每隔 15 秒触发一次
?:只能在日期(Day of Month)和星期几(Day of Week)字段中使用,表示不指定值,在需要指定某一个字段而不关心另一个字段时使用。例如,想要在每月的 20 日触发而不管 20 日是星期几,则在 Day of Week 字段中使用 ?
L:只能在日期(Day of Month)和星期几(Day of Week)字段中使用,表示最后。例如,在 Day of Week 字段使用 5L 表示在最后一个星期四触发
W:只能在日期(Day of Month)字段中使用,表示工作日,系统将在离指定日期的最近的有效工作日触发事件。例如,在 Day of Month 中使用 5W,如果 5 日是星期六,则在 4 日(星期五)触发。如果 5 日是星期天,则在 6 日(周一)触发;如果 5 日是星期一到星期五中的一天,就在 5 日触发。另外,W 的最近寻找不会跨过月份
#:只能在日期(Day of Month)字段中使用,用于指定每月的第几个星期几。例如,2#1 表示某月的第一个星期一

示例:

(1)每天凌晨 1 点执行一次

0 0 1 * * *

(2)每隔5分钟执行一次

0 */5 * * * *

(3)每天下午 2 点到下午 2:05 期间,每 1 分钟执行一次

0 0-5 14 * * *

(4)每周一的早上 7 点执行一次

0 0 7 ? * MON

2.fixedRate

按固定的频率执行任务,从任务开始执行的时间算起,每隔固定的时间间隔重新执行一次,不管前一个任务是否完成

但这里需要注意的是,如果使用默认的方式,不修改线程池参数,即单线程执行,如果前一个任务执行时长大于定时时长,下一个任务的执行会等到上一个任务执行完后立即执行。但是如果增大线程池的线程数量,如果线程数量足够,下一个任务的执行不会被上一个任务所阻塞,而是会在定时结束后就立即执行,即出现任务并行执行的情况

举例:

(1)任务执行时间为 3s,间隔时间为 5s

第一次执行:0s 开始

第二次执行:5s 开始

第三次执行:10s 开始

(2)任务执行时间为 6s,间隔时间为 5s(单线程)

第一次执行:0s 开始

第二次执行:6s 开始

第三次执行:12s 开始

3.fixedDelay

在前一个任务完成后,等待固定的时间间隔再开始执行下一个任务

举例:

任务执行时间为 3s,间隔时间为 5s

第一次执行:0s 开始

第二次执行:3s(任务完成) + 5s(延迟时间) = 8s 开始

第三次执行: 11s(任务完成) + 5s(延迟时间) = 16s 开始

4.initialDelay

在应用启动后,初始延迟一定时间后开始任务,一般会配合 fixedRatefixedDelay 使用,来指定延迟的时间

代码实现

1.在 Springboot 启动类加上 @EnableScheduling 注解,表示启用 Spring 框架中的计划任务调度功能

@SpringBootApplication
@EnableScheduling
public class TimedTaskDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(TimedTaskDemoApplication.class, args);
    }

}

2.编写任务类,在定时任务方法上面加上 @Scheduled 注解,注意在类的上面要加上 @Component 注解。这里根据四种常用的参数分别编写不同的任务

(1)cron

@Component
public class ScheduleTask {
    
    @Scheduled(cron = "*/1 * * * * *") // 每秒执行一次
    public void task1() {
        System.out.println(LocalDateTime.now() + "定时任务");
    }
    
}

测试结果:

image-20240708220943107

(2)fixedRate

@Component
public class ScheduleTask {
    
    private int times = 1;
    
    @Scheduled(fixedRate = 5000) // 延迟 5s
    public void task2() {
        System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
        try {
            Thread.sleep(3000); // 执行 3s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
        times++;
    }
    
}

测试结果:

image-20240708221347489

这里的延迟时间大于任务的执行时间,但当任务的执行时间大于了延迟时间,结果又会不相同,测试代码如下:

@Component
public class ScheduleTask {
    
    private int times = 1;
    
    @Scheduled(fixedRate = 5000) // 延时 5s
    public void task3() {
        System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
        try {
            Thread.sleep(7000); // 执行 7s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
        times++;
    }
    
}

image-20240708221751477

可以看到,设定的延时时间为 5s,而实际上延迟的时间是 7s,原因在于默认线程池大小为 1,任务只能串行执行,后面的任务会受到前面任务执行耗时的影响

这里可以选择开启多线程,在类上方加上 @EnableAsync 注解,对应的方法上方加上 @Async 注解即可

@EnableAsync
@Component
public class ScheduleTask {
    
    @Async
    @Scheduled(fixedRate = 5000) // 延迟 5s
    public void tasks4() {
        System.out.println(LocalDateTime.now() + "定时任务开始,当前线程:" + Thread.currentThread());
        try {
            Thread.sleep(7000); // 执行 7s
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    
}

测试结果:

image-20240708231159285

可以看到此时的延时时间就是 5s

(3)fixedDelay

@Component
public class ScheduleTask {
    
    private int times = 1;
    
    @Scheduled(fixedDelay = 5000) // 延时 5s
    public void task5() {
        System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
        try {
            Thread.sleep(3000); // 执行 3s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
        times++;
    }
    
}

测试结果:

image-20240708231408165

可以看到,下一次任务的开始时间是上一次任务的结束时间 + 延时时间

(4)initialDelay

@Component
public class ScheduleTask {
    
    private int times = 1;
    
    // 初始 1s,延时 5s
    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void task6() {
        System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
        try {
            Thread.sleep(3000); // 执行 7s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
        times++;
    }
    
}

测试结果:

image-20240708232516584

可以看到,是在应用启动后 1s 开始第一次任务,随后每次延时了 5s

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值