最近使用SpringBoot中的scheduled,本地测试好像都无问题,部署在环境后,过一两天发现我的定时任务怎么没有执行。最后经过排查发现了一下需要注意的坑
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 这下正常了 **/
解决办法
//当然了,构建一个合理的线程池也是一个关键,否则提交的任务也会在自己构建的线程池中阻塞 //方法中的线程池在本网站有实现代码,将会在本网站结尾注明 @Scheduled(cron = "0/1 * * * * ? ") public void deleteFile() { ThreadPool.pool.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() { ThreadPool.pool.execute(()->{ log.info("222sync success, time:" + new Date().toString()); }); }
2.把Scheduled配置成多线程执行
@Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //这里设置的线程池是全局线程池,当然你也可以参考下面自己new一个 taskRegistrar.setScheduler(ThreadPool.pool); /**为什么这么说呢? 假设你有4个任务需要每隔1秒执行,而其中三个都是比较耗时的操作可能需要10多秒,而你的语句是这样写的: taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3)); 那么仍然可能导致最后一个任务被阻塞不能定时执行 **/ } }
注:
ThreadPool类(实际只是为了new一个全局的线程池,方便调用,避免每个需要用到的地方new来new去,影响代码阅读)
public class ThreadPool { public static final ThreadPoolHelper pool; static { //此处的32一般根据实际情况设计,电脑的CPU线程数+1 +1是为了尽可以避免假死线程 pool = new ThreadPoolHelper(ThreadPoolHelper.Type.FixedThread,32); } }