本文我们将分享介绍如何基于Spring Boot搭建的项目使用Spring Task定时任务,带领各位小伙伴踩一踩在使用Spring定时任务时所出现的坑,并采用线程池~多线程任务调度的形式对出现的坑加以解决、优化!

对于定时任务,相信各位小伙伴都有所耳闻,甚至有些小伙伴对定时任务的使用已经到了“炉火纯青”的地步!而事实上,在实际的项目、特别是企业级Spring的项目开发中, Spring Task定时任务的使用是相当频繁的。

本文我们将基于前文Spring Boot搭建的标准企业级项目作为奠基,采用注解的形式将Spring Task定时任务应用到项目中,那废话不多讲,咱们直接进入撸码环节!

(1)首先,我们在 com.debug.springboot.server 包目录下建立scheduler包目录,并在其下建立一个通用化的用于编写定时任务的CommonScheduler 类,如下源代码所示,我们建立了三个定时任务,其中每个定时任务执行的时间频率分别为每5s、每6s、每7s执行一次:

/** * spring task-定时任务调度 * @Author:debug (SteadyJack) * @Date: 2019/9/7 11:05 **/@Componentpublic class CommonScheduler { private static final Logger log= LoggerFactory.getLogger(CommonScheduler.class); //定时任务1 @Scheduled(cron = "0/5 * * * * *") public void schedulerOne(){ log.info("---执行定时任务1---"); } //定时任务2 @Scheduled(cron = "0/6 * * * * *") public void schedulerTwo(){ log.info("---执行定时任务2---"); try { //模拟当前定时任务每次执行业务逻辑时需要花费的时间 3s Thread.sleep(3000); }catch (Exception e){e.printStackTrace();} } //定时任务3 @Scheduled(cron = "0/7 * * * * *") public void schedulerThree(){ log.info("---执行定时任务3---"); try { //模拟当前定时任务每次执行业务逻辑时需要花费的时间 4s Thread.sleep(4000); }catch (Exception e){e.printStackTrace();} }}

(2)其中,为了更好的模拟在实际项目开发中 定时任务 执行的业务逻辑,我们假定了每次执行定时任务2时需要花费3s的时间,定时任务3执行业务逻辑时需要花费4s的时间。

理论上,每个定时任务在执行相应的业务逻辑时,是不应该相互影响的,即在理想的情况下,SchedulerOne应当每隔5s执行一次业务逻辑,SchedulerTwo应当每隔6s执行一次业务逻辑,SchedulerThree应当每隔7s执行一次业务逻辑,以此类推。

下面,我们将整个项目运行起来,即可触发这几个定时任务调度的执行。当然,在此之前,我们需要在MainApplication启动类中加入一个注解:@EnableScheduling 即允许定时任务调度的执行,如下图所示:




spring 取消定时任务 spring定时任务缺点_springboot线程池


(3)完了之后,即可将整个项目运行起来了,耐心观察控制台Console的输出信息,即可看到每个定时任务的执行频率,如下图所示:


spring 取消定时任务 spring定时任务缺点_springboot线程池_02


(4)从该控制台Console中,我们可以看到几点信息:

A. 第一点是所有的定时任务确实都已经执行了;

B. 第二点是每个定时任务虽然都已经执行了,但是却不是按照设定的cron来执行,特别是SchedulerOne定时任务1,从上图中会发现第一次执行时是在 12:07:27 ,下次执行时却是在 12:07:35 ,前后间距为8s,而不是5s,这一点在我们看来是不正常的;

C. 除此之外,还有最后一点,即所有定时任务调度的执行竟然都是在同一个“线程”内执行,这是很不可思议的,因为这会导致那些“cron相隔间距很短” 的定时任务出现堵塞的现象,这一点其实就是上述 B第二点出现的现象 的原因所在!

因此我们所讲的 定时任务 在使用的过程中出现的坑,其实就是上述所讲的第二点B跟第三点C,而为了解决这样的问题(坑),下面我们将采用“线程池~多线程”的形式配置定时任务调度的执行策略。

(1)首先,我们在 com.debug.springboot.server 包目录下建立 config 包,然后在该包目录中建立 “定时任务调度-线程池的通用配置类” SchedulerConfig ,其源代码如下所示:

/** * 定时任务调度-线程池配置 * @Author:debug (SteadyJack) * @Date: 2019/9/7 11:12 **/@Configurationpublic class SchedulerConfig { //任务调度线程池配置 @Bean("taskExecutor") public Executor taskExecutor(){ ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor(); //核心线程数 executor.setCorePoolSize(4); //最大核心线程数 executor.setMaxPoolSize(10); //设置队列中等待被调度的任务的数量 executor.setQueueCapacity(8); executor.initialize(); return executor; }}

(2)完了之后,需要在通用的定时任务调度类中加入 该 taskBean 的使用,即主要加入两个注解 @EnableAsync (允许异步执行)、@Async("taskExecutor") (基于指定的配置Bean异步执行相应的业务逻辑),调整后的源代码如下所示:

/** * spring task-定时任务调度 * @Author:debug (SteadyJack) * @Date: 2019/9/7 11:05 **/@Component@EnableAsyncpublic class CommonScheduler { private static final Logger log= LoggerFactory.getLogger(CommonScheduler.class); //定时任务1 @Scheduled(cron = "0/5 * * * * *") @Async("taskExecutor") public void schedulerOne(){ log.info("---执行定时任务1---"); } //定时任务2 @Scheduled(cron = "0/6 * * * * *") @Async("taskExecutor") public void schedulerTwo(){ log.info("---执行定时任务2---"); try { //模拟当前定时任务每次执行业务逻辑时需要花费的时间 3s Thread.sleep(3000); }catch (Exception e){e.printStackTrace();} } //定时任务3 @Scheduled(cron = "0/7 * * * * *") @Async("taskExecutor") public void schedulerThree(){ log.info("---执行定时任务3---"); try { //模拟当前定时任务每次执行业务逻辑时需要花费的时间 4s Thread.sleep(4000); }catch (Exception e){e.printStackTrace();} }}

(3)将整个项目运行起来,观察控制台Console的输出,会发现此时控制台的输出信息跟没使用“线程池~多线程”时是两种结果,如下图所示:


spring 取消定时任务 spring定时任务缺点_springboot多线程_03


至此,关于Spring Task定时任务调度在Spring Boot项目中的使用我们已经介绍、实战完毕了。实不相瞒,在实际的企业级应用开发中,加入“线程池~多线程”的任务配置形式才是使用@Scheduled定时任务调度的正确方式!