一、使用定时任务
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-------目录帖