Spring boot定时任务@Scheduled

1.前言:

@Scheduled 参数可以接受两种定时的设置,一种是我们常用的cron="*/6 * * * * ?",一种是 fixedRate = 6000,两种都表示每隔六秒打印一下内容:
@Scheduled(cron = “0 0 0 * * ?”) // 每日凌晨
@Scheduled(cron = “0 0/10 * * * ?”) // 每10分钟
@Scheduled(cron = “0 30 8-18 ? * MON-FRI”) //每周一到周五,8:30到18:30

@Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
fixedDelay:在上一次执行完之后等xxx毫秒(xxx就是fixedDelay = 5000中的5000)再执行,循环下去,上一次执行多久都没关系 ,反正上一次执行完后xxx毫秒我才执行
@Scheduled(fixedDelay = 5000, initialDelay=8000)
initialDelay:第一次运行次要等xxx毫秒才能执行,比如:假设原来是14:00:00 开始执行这个,但是设置了initialDelay=8000,那么要在8000毫秒后才能执行这个方法,也就是14:00:08才真正执行
@Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
fixedRate:这个有点蛋疼…,举个例子:比如:假设有5个执行时间点 间隔是5000毫秒:分别是:
T1:14:00:00
T2:14:00:05
T3:14:00:10
T4:14:00:15
T5:14:00:20
如果T1执行时间花了4秒,也就是到了14:00:04,那么你会看到14:00:05分就开始执行了T2,很正常,此时T1结束时间和T2开始时间只差1000毫秒,没毛病
如果T1执行时间花了8秒,怎么办?这时T1执行完的时间是14:00:08,已经覆盖了T2的时间,T2在14:00:05到14:00:08是等等状态。现在是14:00:08,看起来接着是T3执行,
但实际不是,而是立马执行T2,立马执行T2,立马执行T2(T2说:我不管,T1搞我超时了,我无论也是执行),这时你会发现T2的执行时间(也就是第2次执行时间 )是:14:00:08,真的是立马。。。
如此类推,只要时执行时间被覆盖了的,到它了就立马执行
@Scheduled(initialDelay=8000, fixedRate=5000) :第一次延迟8秒后执行,之后按fixedRate的规则每5秒执行一次
 

2.pom包配置:

pom包里面只需要引入springboot starter包即可

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
        <relativePath/>
    </parent>

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

注意:如果在Windows上运行SpingBoot上面的pom.xml的配置即可,但是要打成jar包上传到Linux上运行的话还需要添加如下配置

    <build>
        <finalName>SpringBootTest</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.启动类启用定时:

在启动类上面加上@EnableScheduling即可开启定时

@SpringBootApplication
@EnableScheduling
public class Application {

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

4.创建定时任务实现类:

定时任务1:

@Component
public class test1Task {

    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    private void process(){
        System.out.println("this is scheduler task runing  "+(count++));
    }

}

定时任务2:

@Component
public class test2Task {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 6000)
    public void reportCurrentTime() {
        System.out.println("现在时间:" + dateFormat.format(new Date()));
    }

}

运行结果:

this is scheduler task runing  0
现在时间:14:23:17
this is scheduler task runing  1
现在时间:14:23:23
this is scheduler task runing  2
现在时间:14:23:29
this is scheduler task runing  3
现在时间:14:23:35

5.补充:cron表达式

字段允许值允许的特殊字符备注
0-59, - * /标准实现不支持此字段。linux crontab 命令就不支持,最小的执行时间是一分钟,如果非想实现秒级别的可参考:linux crontab 实现每秒执行
0-59, - * /
小时0-23, - * /
日期1-31, - * ? / L W C需要考虑月的天数,如有28天或30天的,? L W只有部分软件实现了
月份1-12 或者 JAN-DEC, - * /
星期1-7 或者 SUN-SAT, - * ? / L C #? L W只有部分软件实现了。Linux和Spring的允许值为0-7,0和7为周日;Quartz的允许值为1-7,1为周日
年(可选)留空, 1970-2099, - * /标准实现不支持此字段。linux crontab 命令就不支持

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?

"0 0 10,14,16 * * ?" 每天上午10点,下午2点,4点
"0 0/30 9-17 * * ?" 朝九晚五工作时间内每半小时
"0 0 12 ? * WED" 表示每个星期三中午12点 
"0 0 12 * * ?" 每天中午12点触发 
"0 15 10 ? * *" 每天上午10:15触发 
"0 15 10 * * ?" 每天上午10:15触发 
"0 15 10 * * ? *" 每天上午10:15触发 
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发 
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 
"0 15 10 15 * ?" 每月15日上午10:15触发 
"0 15 10 L * ?" 每月最后一日的上午10:15触发 
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天

“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样

“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

“L”字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示一个月的倒数第6天,“FRIL”表示一个月的最后一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
例子:
    # 每月的最后1天
    0 0 L * * *

    说明:
    Linux
    *    *    *    *    *
    -    -    -    -    -
    |    |    |    |    |
    |    |    |    |    +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
    |    |    |    +---------- month (1 - 12) OR jan,feb,mar,apr ...
    |    |    +--------------- day of month (1 - 31)
    |    +-------------------- hour (0 - 23)
    +------------------------- minute (0 - 59)

补充:常用命令
# 编辑定时任务
crontab -e
# 显示定时任务
crontab -l
# 查看执行的日志
cat /var/log/cron

还可以用如下工具验证下次执行的时间https://tool.lu/crontab/ 非常的方便
 

6.遇到的坑:

注:有一个需要每个月的最后一天晚上的九点半执行调度任务,本以为这样写@Scheduled(cron="0 38 21 L * ?")就OK了,结果报错Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'process': For input string: "L"
原因:错误的根源是 Spring 的表达式只是 cron表达式的子集,它不包含year字段,并且不能使用所有特殊字符,比如L和W,大部分的文档有误导。
解决:

@Scheduled(cron = "0 38 21 28-31 * ?")
public void execute() {
	final Calendar c = Calendar.getInstance();
	if (c.get(Calendar.DATE) == c.getActualMaximum(Calendar.DATE)) {
		//是最后一天
		System.out.println("是最后一天,开始执行调度任务");
	}
}
// 或者:
@Scheduled(cron = "0 38 21 28-31 * ?")
public void reptilian() {
    final Calendar calendar = Calendar.getInstance();
    //如果不是最后一天
    if (!(calendar.get(Calendar.DATE) == calendar.getActualMaximum(Calendar.DATE))) {
        return;
    }
    // 执行业务逻辑代码
}

版权声明:本文为m0_37739193原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。