SpringBoot中使用@scheduled定时执行任务需要注意的坑

spring boot: 计划任务@ EnableScheduling和@Scheduled

@Scheduled中的参数说明

@Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行; 

@Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行;

@Scheduled(initialDelay=1000, fixedDelay=2000):第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行; 

@Scheduled(cron="* * * * * ?"):按cron规则执行。  

常用Cron表达式(  秒/分/时/日/月/周/年  )

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时

0 0 12 ? * WED 表示每个星期三中午12点

"0 0 12 * * ?" 每天中午12点触发

"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触发

"0 15 10 ? * *" 每天上午10:15触发

"0 15 10 * * ?" 每天上午10:15触发

"0 15 10 * * ? *" 每天上午10:15触发

"0 15 10 * * ? 2005" 2005年的每天上午10:15触发

  

实例:

service

复制代码

 1 package ch2.scheduler;
2
3 //时间处理,时间格式化
4 import java.util.Date;
5 import java.text.SimpleDateFormat;
6
7
8 //spring计划任务声明(针对方法声明)
9 import org.springframework.scheduling.annotation.Scheduled;
10 //spring组件声明
11 import org.springframework.stereotype.Service;
12
13
14 //组件什么
15 @Service
16 public class SchedulerService {
17
18 //创建日期模板
19 private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH::MM::ss");
20
21
22 //每5秒钟执行一次
23 @Scheduled(fixedRate = 5000)
24 public void reportCurrentTime()
25 {
26 System.out.println("每五秒执行一次: " + dateFormat.format( new Date() ));
27 }
28
29 //按照cron属性指定执行时间:秒分时
30 @Scheduled(cron = "0 34 18 ? * *")
31 public void fixTimeExecution()
32 {
33 System.out.println("在指定的时间内执行: " + dateFormat.format( new Date()) );
34 }
35
36
37 }
复制代码 config 复制代码 1 package ch2.scheduler;
2
3 //自动引入包
4 import org.springframework.context.annotation.ComponentScan;
5 //配置类声明
6 import org.springframework.context.annotation.Configuration;
7
8 //计划任务类声明
9 import org.springframework.scheduling.annotation.EnableScheduling;
10
11
12 //配置类声明
13 @Configuration
14 //自动引入包
15 @ComponentScan("ch2.scheduler")
16 //通过@EnableScheduling开启对计划任务的支持
17 @EnableScheduling
18 public class TaskSchedulerConfig {
19
20
21
22 }
复制代码 main 复制代码 1 package ch2.scheduler;
2 //引入容器
3 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4
5 public class Main {
6
7
8 public static void main(String[] args)
9 {
10 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskSchedulerConfig.class);
11 //SchedulerService schedulerService = context.getBean(SchedulerService.class);
12
13
14 }
15 }

要注意什么坑

不绕弯子了,直接说这个坑是啥:

SpringBoot使用@scheduled定时执行任务的时候是在一个单线程中,如果有多个任务,其中一个任务执行时间过长,则有可能会导致其他后续任务被阻塞直到该任务执行完成。也就是会造成一些任务无法定时执行的错觉

可以通过如下代码进行测试:

    @Scheduled(cron = "0/1 * * * * ? ")
public void deleteFile() throws InterruptedException {
log.info("111delete success, time:" + new Date().toString());
Thread.sleep(1000 * 5);//模拟长时间执行,比如IO操作,http请求
} @Scheduled(cron = "0/1 * * * * ? ")
public void syncFile() {
log.info("222sync success, time:" + new Date().toString());
} /**输出如下:
[pool-1-thread-1] : 111delete success, time:Mon Nov 26 20:42:13 CST 2018
[pool-1-thread-1] : 222sync success, time:Mon Nov 26 20:42:18 CST 2018
[pool-1-thread-1] : 111delete success, time:Mon Nov 26 20:42:19 CST 2018
[pool-1-thread-1] : 222sync success, time:Mon Nov 26 20:42:24 CST 2018
[pool-1-thread-1] : 222sync success, time:Mon Nov 26 20:42:25 CST 2018
[pool-1-thread-1] : 111delete success, time:Mon Nov 26 20:42:25 CST 2018
上面的日志中可以明显的看到syncFile被阻塞了,直达deleteFile执行完它才执行了
而且从日志信息中也可以看出@Scheduled是使用了一个线程池中的一个单线程来执行所有任务的。
**/ /**如果把Thread.sleep(1000*5)注释了,输出如下:
[pool-1-thread-1]: 111delete success, time:Mon Nov 26 20:48:04 CST 2018
[pool-1-thread-1]: 222sync success, time:Mon Nov 26 20:48:04 CST 2018
[pool-1-thread-1]: 222sync success, time:Mon Nov 26 20:48:05 CST 2018
[pool-1-thread-1]: 111delete success, time:Mon Nov 26 20:48:05 CST 2018
[pool-1-thread-1]: 111delete success, time:Mon Nov 26 20:48:06 CST 2018
[pool-1-thread-1]: 222sync success, time:Mon Nov 26 20:48:06 CST 2018
这下正常了
**/

解决办法

1.将@Scheduled注释的方法内部改成异步执行
如下:

//当然了,构建一个合理的线程池也是一个关键,否则提交的任务也会在自己构建的线程池中阻塞
ExecutorService service = Executors.newFixedThreadPool(5); @Scheduled(cron = "0/1 * * * * ? ")
public void deleteFile() {
service.execute(() -> {
log.info("111delete success, time:" + new Date().toString());
try {
Thread.sleep(1000 * 5);//改成异步执行后,就算你再耗时也不会印象到后续任务的定时调度了
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} @Scheduled(cron = "0/1 * * * * ? ")
public void syncFile() {
service.execute(()->{
log.info("222sync success, time:" + new Date().toString());
});
}

2.把Scheduled配置成成多线程执行

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//当然了,这里设置的线程池是corePoolSize也是很关键了,自己根据业务需求设定
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); /**为什么这么说呢?
假设你有4个任务需要每隔1秒执行,而其中三个都是比较耗时的操作可能需要10多秒,而你上面的语句是这样写的:
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
那么仍然可能导致最后一个任务被阻塞不能定时执行
**/
}
}
  
上一篇:BR(BoomerangRobot)机器人项目


下一篇:(Beta)Let's-M2后分析报告