@Scheduled 定时任务经验分享

本文介绍了SpringBoot如何使用@EnableScheduling开启定时任务,并通过@Scheduled注解配置执行逻辑。当任务耗时导致阻塞时,通过@Async实现异步执行,确保每个定时任务能按预定频率独立执行。同时提到了线程池的配置和使用,以应对高并发场景。
摘要由CSDN通过智能技术生成

在开发中,有时候会遇到定时推送的需求,例如定时发送推送、定时发送邮件等等。SpringBoot为我们内置了定时任务,我们只需要一个注解就可以开启定时为我们所用了。

开启定时任务

在入口类LogApplication中添加 @EnableScheduling 注解,添加注解后SpringBoot就已经认定了我们要使用定时任务来完成一些业务逻辑了,内部会对应原始配置定时任务添加对应的配置文件。

@SpringBootApplication
@EnableScheduling    +    // 启用定时任务
public class LogApplication {

@Scheduled

配置定时任务非常简单,只需要在需要定时执行的方法上 添加 @Scheduled 注解即可 。 注意 , 该类上需要打上组件型注解 ,例如 @Componet ,这样该类才会被注入到 Spring 容器中进行管理,用来标明这是一个被Spring管理的Bean, @Scheduled 才会生效。

当然一般会使用@Coponent的衍生注解:@Repository, @Service, @Controller来替代,这里不展开讲了。

@Service  +
public class DayLogTaskImpl implements DayLogTask {
    @Scheduled(cron = "* * * * * ?")  +
    public void test1() {

现在来测试一下代码:

@Scheduled(cron = "* * * * * ?")
     public void test1() {
        /**
         * 每秒执行一次
         */
        logger.info("scheduler1 执行: " + System.currentTimeMillis());
    }

运行结果:

2022-02-14 13:46:50.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 执行: 1644828410000
2022-02-14 13:46:51.003  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 执行: 1644828411003
2022-02-14 13:46:52.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 执行: 1644828412000
2022-02-14 13:46:53.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 执行: 1644828413000

可以看到打印出的时间相隔都大约是1000毫秒左右,符合定义的每秒执行一次。

@Scheduled 注解参数说明

cron:该参数接收一个cron表达式,cron表达式是一个字符串,如  表示每5秒执行一次。

zone: 时区。默认为服务器所在时区,接收类型为 java.util.TimeZone。

fixedDelay:上一次执行完毕时间点之后多长时间再执行。如:

@Scheduled(fixedDelay = 10000) //上一次执行完毕时间点之后10秒再执行

fixedRate: 表示在上次执行开始之后多久后再次执行。用法同上。

initialDelay: 表示第一次任务执行延迟多久。用法同上。

@Scheduled的线程问题

@Scheduled 默认是单线程执行的,多个 @Scheduled 任务都用的同一个线程。如果某个任务是个耗时的操作,那么其它定时任务都会等待该任务的执行完成,从而造成堵塞。

比如下面我们给出一个例子:两个无关的定时任务: 打印日志print 和 生成表格generate ,都是需要每秒触发一次。如果 generate 执行一次需要耗时 3 秒,则 print 和 generate 都要等这一次的 generate 执行后才能再执行,这就达不到任务 print、generate 每秒执行一次的效果了。

我们用代码来测试一下

我们用Thread.sleep()模拟执行方法时的耗时。两者都是每秒执行一次,其中test1睡眠3秒模拟耗时。

@Scheduled(cron = "* * * * * ?")
    public void test1() {
        /**
         * 每秒执行一次
         */
        logger.info("test1执行并睡眠3秒: " + System.currentTimeMillis());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("test1执行结束: " + System.currentTimeMillis());
    }

 @Scheduled(cron = "* * * * * ?")
    public void test2() {
        /**
         * 每秒执行一次
         */
        logger.info("test2 执行: " + System.currentTimeMillis());
    }

运行结果:

2022-02-14 14:09:41.004  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644829781004
2022-02-14 14:09:44.007  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644829784007
2022-02-14 14:09:44.010  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644829784010
2022-02-14 14:09:45.005  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644829785005
2022-02-14 14:09:48.007  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644829788007
2022-02-14 14:09:48.008  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644829788008
2022-02-14 14:09:49.009  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644829789009
2022-02-14 14:09:49.011  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644829789011
2022-02-14 14:09:52.012  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644829792012
2022-02-14 14:09:52.013  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644829792013
2022-02-14 14:09:53.005  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644829793005

可以看到执行的线程都是[ scheduling-1],并且test2执行间隔并不固定是设置好的1s,而是 有时是1s,有时是3s,4s。

那么,如何能让每个定时任务按照配置好的定时规则准时执行呢?这就需要我们将定时任务配置成多线程的方式。

使用 @Async 异步执行方法

与添加定时任务相同,在入口类中添加 @EnableAsync,开启异步执行。在定时任务上添加 @Async 注解,标识该方法异步执行。方法代码与上面的相同。

@Async  +
    @Scheduled(cron = "* * * * * ?")
    public void test1() {

这时候我们再执行代码看看效果:

2022-02-14 14:45:04.001  INFO 2608 --- [         task-4] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644831904001
2022-02-14 14:45:04.001  INFO 2608 --- [         task-1] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644831904001
2022-02-14 14:45:05.002  INFO 2608 --- [         task-2] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644831905002
2022-02-14 14:45:05.002  INFO 2608 --- [         task-5] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644831905002
2022-02-14 14:45:05.003  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644831905003
2022-02-14 14:45:06.001  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644831906001
2022-02-14 14:45:06.001  INFO 2608 --- [         task-6] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644831906001
2022-02-14 14:45:06.008  INFO 2608 --- [         task-3] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644831906008
2022-02-14 14:45:07.001  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 执行: 1644831907001
2022-02-14 14:45:07.001  INFO 2608 --- [         task-4] club.yunzhi.log.task.DayLogTask          : test1执行结束: 1644831907001
2022-02-14 14:45:07.002  INFO 2608 --- [         task-7] club.yunzhi.log.task.DayLogTask          : test1执行并睡眠3秒: 1644831907002

虽然方法执行需要耗时 3 秒,但是 每个任务还是按照每秒钟触发一次准时执行了。 通过前面的 线程号 可以看出, 任务的执行使用了不同的线程 。test2用的是scheduling-1的线程号,每秒一次。而test1异步则用了多个线程号。

@Async 默认线程池个数为 8 个。

默认的 @Async 可以应付一般的场景,但如果是并发量比较高的情况下,就会存在一定风险。例如开销过大、内存溢出等。为使服务运行稳定,我们可以自定义配置线程池,然后让给需要异步执行的方法指定用该线程池运行。

具体可以参考这篇文章: https://www.interhorse.cn/a/3...

目前的项目中定时任务执行的方法较快,且间隔在分钟级别,所以没有用到多线程定时任务。

以上为定时任务的相关内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值