第十二节 SprnigBoot使用定时任务

一、使用定时任务

        SpringBoot Starter包中已经内置了定时任务的方法。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        在yml中配置自定义参数。定义一下执行任务的周期。每隔6秒执行一次。

        常见的cron执行参数(下面的三个是我真实在项目中用过的,因此特别记录下来,很务实)。

每隔30秒执行一次:*/30 * * * * ?    开发人员自测

每隔10分钟执行一次:0 */10 * * * ?   给测试小姐姐用

每月1号凌晨1点执行一次:0 0 1 1 * ?  每月推送一次月报

每天凌晨5点执行一次:0 0 5 * * ?     生产上使用

#自定义参数
define:
  quartz:
    cron: "*/6 * * * * ?"

         下面的定时任务打印当前线程的 hash码。

package com.zhoutianyu.learnspringboot.quartz;

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class BaseQuartZ {

    @Scheduled(cron = "${define.quartz.cron}")
    public void schedule() {
        System.out.println(Thread.currentThread().hashCode());
    }

}

        从打印结果来看,是同一个线程在跑这个定时任务。理所当然的,有一天,测试小姐姐跑过来对我说,为什么定时任务5分钟都没刷新过一次,明明设计的是30秒跑一次。经过我的排查,惊讶的发现所有的定时任务都是同一个线程在跑。

        

        如果有多个定时任务在同时执行,那么势必会顺序执行。同事的某个定时任务从日志里面知道跑了8万多毫秒,大概是是1分多钟,再加上其他的定时任务,一个线程压根跑不过来。因此,就出现了5分钟之内,我的定时任务都没刷新的情况。

        临时的解决方案:构造一个线程池,然后使用@EnableAsync注解开启异步。在每个定时任务的方法上,增加@Async注解即可。

@Component
public class ThreadPoolConfig {

    @Bean(value = "threadPoolTaskExecutor")
    public Executor getExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
        executor.setCorePoolSize(5);
        //线程池维护线程的最大数量
        executor.setMaxPoolSize(10);
        //允许的空闲时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        executor.setKeepAliveSeconds(60);
       //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("demo.tyzhou-");
        //缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
        executor.setQueueCapacity(10);
        /**
         * 拒绝task的处理策略
         * CallerRunsPolicy使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
         * AbortPolicy丢掉这个任务并且抛出
         * DiscardPolicy线程池队列满了,会直接丢掉这个任务并且不会有任何异常
         * DiscardOldestPolicy队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
         */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
package com.zhoutianyu.learnspringboot.quartz;

@Component
@EnableScheduling
@EnableAsync
public class CronDemo {

    @Async
    @Scheduled(cron = "${define.quartz.cron}")
    public void schedule() {
        System.out.println(Thread.currentThread().hashCode());
    }

    @Async
    @Scheduled(cron = "${define.quartz.cron}")
    public void schedule2() {
        System.out.println(Thread.currentThread().hashCode());
    }
    
}

        从执行结果来看,每个定时任务操作它的线程都不一样,这样就达到了我们希望得到的结果。 

        

        虽然上述的做法解决了问题,但是后来主管还是把我这套给替换了。现在项目中使用了开源的定时任务框架Quartz,对其做了封装。当然了,也不要灰心,代码迭代更新在所难免。

二、分布式锁

        由于现在都是微服务,每个系统都会部署很多个节点。每个节点都是不同的服务器,即不同的JVM进程。因此可能在同一时间同时运行了相同的定时任务,因此必须考虑加锁的情况。因此涉及到分布式锁。

        分布式锁的常见实现方式有Redis的实现方案,很简单。另外一种就是Redisson的分布式锁,比较方便使用。

三、源码下载

        本章节项目源码:点我下载源代码

        目录贴:跟着大宇学SpringBoot-------目录帖

posted @ 2022-07-17 12:14  小大宇  阅读(33)  评论(0编辑  收藏  举报