【spring-Scheduled】浅谈spring scheduled定时任务
#Round 1:使用Scheduled定义一个定时任务
定义一个java类,在需要定时执行的方法上加上@Scheduled
注解
以下例子为使用cron表达式启动task1的方法,方法逻辑为每3s打印1句log,打印10句log以后退出。
public class Task1 { @Scheduled(cron = "0/5 * * * * ?") protected void doTask1() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task1 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task1 is finished."); } }
配置并启动springboot可以看到以下log,可见task1
被成功启动,但是有两个注意点:
- 启动该定时任务的线程名为
pool-1-thread-1
,可以理解为默认线程池pool
的第一个线程 - 定时任务周期为5s,定时任务逻辑总时长为30s,但是第二轮定时任务该启动的时候并没有启动,而是等待直到第一轮的定时任务执行完成以后再开始第二轮的逻辑。
pool-1-thread-1 is running task1 @0times pool-1-thread-1 is running task1 @1times pool-1-thread-1 is running task1 @2times pool-1-thread-1 is running task1 @3times pool-1-thread-1 is running task1 @4times pool-1-thread-1 is running task1 @5times pool-1-thread-1 is running task1 @6times pool-1-thread-1 is running task1 @7times pool-1-thread-1 is running task1 @8times pool-1-thread-1 is running task1 @9times pool-1-thread-1 task1 is finished. pool-1-thread-1 is running task1 @0times pool-1-thread-1 is running task1 @1times pool-1-thread-1 is running task1 @2times ······
#Round 2:使用Scheduled定义两个定时任务
我们将上面的类Task1稍微改一下新写一个Task2,新增一个相同的方法,并且在这个方法上加上相同的注解。
这样我们就有了两个定时任务,并且都是每5s启动一次,执行逻辑均为每3s打印1句log,打印10句以后退出。
public class Task2 { @Scheduled(cron = "0/5 * * * * ?") protected void doTask2_1() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task2-1 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task2-1 is finished."); } @Scheduled(cron = "0/5 * * * * ?") protected void doTask2_2() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task2-2 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task2-2 is finished."); } }
配置并启动springboot以后我们看到了以下输入内容:
但是这个输入结果好像和我们预想的不太一样,从输入的log中我们可以看出:
- 首先开启的是
task2-1
(当然也有可能是task2-2,总之只会启动一个)
- 然后再
task2-1
执行结束以后task2-2
才开始执行 - 并且两个定时任务是交替执行的
pool-1-thread-1 is running task2-1 @0times pool-1-thread-1 is running task2-1 @1times pool-1-thread-1 is running task2-1 @2times pool-1-thread-1 is running task2-1 @3times pool-1-thread-1 is running task2-1 @4times pool-1-thread-1 is running task2-1 @5times pool-1-thread-1 is running task2-1 @6times pool-1-thread-1 is running task2-1 @7times pool-1-thread-1 is running task2-1 @8times pool-1-thread-1 is running task2-1 @9times pool-1-thread-1 task2-1 is finished. pool-1-thread-1 is running task2-2 @0times pool-1-thread-1 is running task2-2 @1times pool-1-thread-1 is running task2-2 @2times pool-1-thread-1 is running task2-2 @3times pool-1-thread-1 is running task2-2 @4times pool-1-thread-1 is running task2-2 @5times pool-1-thread-1 is running task2-2 @6times pool-1-thread-1 is running task2-2 @7times pool-1-thread-1 is running task2-2 @8times pool-1-thread-1 is running task2-2 @9times pool-1-thread-1 task2-2 is finished. pool-1-thread-1 is running task2-1 @0times pool-1-thread-1 is running task2-1 @1times pool-1-thread-1 is running task2-1 @2times ······
为什么两个两个定时任务会同步执行呢?
仔细看一下log我们发现两个定时任务启动的线程都是pool-1-thread-1
。
疑问1:是不是因为默认的用于执行定时任务的线程池大小只有1呢?
疑问2:而在上一个例子中,只有一个定时任务,但是一次任务未结束第二次任务不开启,是不是也是因为线程池不够用呢?
#Round3:修改线程池大小使多个定时任务同时执行
在org.springframework.scheduling
包中我们发现了一个接口SchedulingConfigurer
,字面意思计划任务配置,应该就是用来修改定时任务线程相关的内容。
接下来我们就用它来配置我们的springboot
,实现这个类并实现configureTasks
方法,通过setScheduler
方法给scheduledTaskRegistrar
对象设置一个线程池,线程数量设为5.
(注意此处因为是定时任务,所以必须使用Scheduled
线程池,如果使用fixed
和cached
线程池均会发生异常)
@Configuration public class TaskConfigurator implements SchedulingConfigurer { @Bean public Task2 task2() { return new Task2(); } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }
再次启动springboot我们看到了以下log输出,可见两个定时任务同时开启,都在执行10次以后结束,并且都在结束以后开启第二轮。
看来上面 疑问1 得到了解释,确实是因为线程池的数量限制了同时执行的任务。
然而在线程数量够用的情况下,为什么第二轮的逻辑没有启动呢?(疑问2 仍然没有得到合理的解释)
pool-1-thread-1 is running task2-2 @0times pool-1-thread-2 is running task2-1 @0times pool-1-thread-1 is running task2-2 @1times pool-1-thread-2 is running task2-1 @1times pool-1-thread-1 is running task2-2 @2times pool-1-thread-2 is running task2-1 @2times pool-1-thread-1 is running task2-2 @3times pool-1-thread-2 is running task2-1 @3times pool-1-thread-1 is running task2-2 @4times pool-1-thread-2 is running task2-1 @4times pool-1-thread-1 is running task2-2 @5times pool-1-thread-2 is running task2-1 @5times pool-1-thread-1 is running task2-2 @6times pool-1-thread-2 is running task2-1 @6times pool-1-thread-1 is running task2-2 @7times pool-1-thread-2 is running task2-1 @7times pool-1-thread-1 is running task2-2 @8times pool-1-thread-2 is running task2-1 @8times pool-1-thread-1 is running task2-2 @9times pool-1-thread-2 is running task2-1 @9times pool-1-thread-1 task2-2 is finished. pool-1-thread-2 task2-1 is finished. pool-1-thread-2 is running task2-1 @0times pool-1-thread-3 is running task2-2 @0times pool-1-thread-2 is running task2-1 @1times pool-1-thread-3 is running task2-2 @1times ······
#Round4:异步任务,前一轮任务未结束不影响第二轮任务启动
我们继续在org.springframework.scheduling
包里找找,发现有一个@Async
和@EnableAsync
注解,看样子就是用来允许定时任务异步执行的。
我们修改一下Task2
,得到Task3
,其实只是在方法上加上了@Async
注解:
public class Task3 { @Async @Scheduled(cron = "0/5 * * * * ?") protected void doTask3_1() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task3-1 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task3-1 is finished."); } @Async @Scheduled(cron = "0/5 * * * * ?") protected void doTask3_2() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task3-2 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task3-2 is finished."); } }
同时修改一下springboot的配置文件,在类上加上@EnableAsync
注解
@EnableAsync @Configuration public class TaskConfigurator implements SchedulingConfigurer { @Bean public Task3 task3() { return new Task3(); } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }
配置好了springboot并启动后我们看到了以下log输出:
确实和我们预想的一样,加了@Async
和@EnableAsync
注解以后定时任务异步执行了,前一轮未结束不影响新的一轮任务开启。到此上面的疑问2 也得到了解释,确实是因为任务的同步异步问题导致的。
task-1 is running task3-1 @0times task-2 is running task3-2 @0times task-1 is running task3-1 @1times task-2 is running task3-2 @1times task-3 is running task3-1 @0times ······ task-6 is running task3-2 @6times task-4 is running task3-2 @8times task-3 is running task3-1 @8times task-8 is running task3-1 @5times task-7 is running task3-2 @5times task-2 task3-2 is finished. task-2 is running task3-2 @0times task-1 task3-1 is finished. task-1 is running task3-1 @0times task-5 is running task3-1 @7times ······
但是从log中发现,明明只配了5个线程,为什么却出现了task-8
呢?而且和之前的pool-1-thread-x
的命名方式也不一样了,应该不是同一个线程池。
疑问3:莫非同步线程池和异步线程池是两个相互独立的?
我们继续在org.springframework.scheduling
包里找找,发现了接口AsyncConfigurer
,根据刚刚的经验SchedulingConfigurer
是用来配置同步任务线程池的,那么接口AsyncConfigurer
是不是用来配置异步任务线程池的呢?
我们修改springboot配置文件,实现接口AsyncConfigurer
,并实现其方法getAsyncExecutor
:
@Configuration public class TaskConfigurator implements SchedulingConfigurer, AsyncConfigurer { @Bean public Task3 task3() { return new Task3(); } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } @Override public Executor getAsyncExecutor() { return Executors.newScheduledThreadPool(5); } }
配置完成启动springboot工程,我们看到以下log:
哈哈,执行的线程名又恢复成了pool-2-thread-x
的格式,这里的pool-2应该是指第二个线程池吧。
确实和我们想的一样,这个接口确实是用来配置异步任务线程池的。
我们最初的目的虽然达到了,但是在实际的生产中会有一个问题发生:
-
在一个项目中同步任务的数量是我们可以预见的,也就是说我们定时任务的数量设成和线程池大小一样是没有任何问题的,因为在一轮没有执行完成时不会开启下一轮任务。
-
而异步任务由于两轮任务互不影响,而上一轮任务什么时候执行完成时无法预见的,所以执行异步任务的线程池需要比异步定时任务的数量多,不然就会占满线程导致新一轮或者共用异步线程池的定时任务无法开启。
疑问4:如何让多个异步定时任务运行在各自的线程池中呢?
pool-2-thread-1 is running task3-1 @0times pool-2-thread-2 is running task3-2 @0times pool-2-thread-1 is running task3-1 @1times ······ pool-2-thread-5 is running task3-1 @6times pool-2-thread-3 is running task3-2 @8times pool-2-thread-4 is running task3-1 @8times pool-2-thread-2 task3-2 is finished. pool-2-thread-2 is running task3-2 @0times pool-2-thread-1 task3-1 is finished. pool-2-thread-1 is running task3-1 @0times pool-2-thread-5 is running task3-1 @7times pool-2-thread-3 is running task3-2 @9times pool-2-thread-4 is running task3-1 @9times pool-2-thread-2 is running task3-2 @1times pool-2-thread-1 is running task3-1 @1times pool-2-thread-5 is running task3-1 @8times pool-2-thread-3 task3-2 is finished. pool-2-thread-3 is running task3-2 @0times pool-2-thread-4 task3-1 is finished. pool-2-thread-4 is running task3-1 @0times pool-2-thread-1 is running task3-1 @2times
#Final Round:异步任务运行在各自的线程池中
回过头来看一下@Async
注解,它允许有一个参数默认为空,从注释中可以发现可以通过这个参数为异步任务指定Executor或TaskExecutor
那我们修改Task3得到Task4,在两个方法的注解上加上线程池的spring IOC对象的name:
分别使用TaskExecutorPool1
和TaskExecutorPool2
public class Task4 { @Async("TaskExecutorPool1") @Scheduled(cron = "0/5 * * * * ?") protected void doTask4_1() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task4-1 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task4-1 is finished."); } @Async("TaskExecutorPool2") @Scheduled(cron = "0/5 * * * * ?") protected void doTask4_2() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " is running task4-2 @" + i + "times"); Thread.sleep(3000); } System.out.println(Thread.currentThread().getName() + " task4-2 is finished."); } }
修改springboot配置文件,添加两个线程池并命名为TaskExecutorPool1
和TaskExecutorPool2
@EnableAsync @Configuration public class TaskConfigurator implements SchedulingConfigurer, AsyncConfigurer { @Bean("TaskExecutorPool1") public ThreadPoolTaskExecutor taskExecutor1() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setThreadNamePrefix("@@TaskExecutor1-"); taskExecutor.setCorePoolSize(5); taskExecutor.setMaxPoolSize(10); taskExecutor.initialize(); return taskExecutor; } @Bean("TaskExecutorPool2") public ThreadPoolTaskExecutor taskExecutor2() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setThreadNamePrefix("@@TaskExecutor2-"); taskExecutor.setCorePoolSize(5); taskExecutor.setMaxPoolSize(10); taskExecutor.initialize(); return taskExecutor; } @Bean public Task4 task4() { return new Task4(); } }
配置完成启动springboot,看到了我们想要的log输出:
- 两个异步定时任务在各自的线程池中运行互不影响
- 从线程名可以看出确实是我们指定的线程
- 由于线程池设置的核心线程数为5,运行时使用的线程数也为5
到此为止,我们最后一个疑问(疑问4)也得到了解释。
@@TaskExecutor1-1 is running task4-1 @0times @@TaskExecutor2-1 is running task4-2 @0times @@TaskExecutor1-1 is running task4-1 @1times @@TaskExecutor2-1 is running task4-2 @1times ······ @@TaskExecutor2-5 is running task4-2 @3times @@TaskExecutor1-5 is running task4-1 @3times @@TaskExecutor2-2 is running task4-2 @8times @@TaskExecutor1-2 is running task4-1 @8times @@TaskExecutor1-4 is running task4-1 @5times @@TaskExecutor2-4 is running task4-2 @5times @@TaskExecutor1-1 task4-1 is finished. @@TaskExecutor1-1 is running task4-1 @0times @@TaskExecutor2-1 task4-2 is finished. @@TaskExecutor2-1 is running task4-2 @0times @@TaskExecutor2-3 is running task4-2 @7times @@TaskExecutor1-3 is running task4-1 @7times @@TaskExecutor1-5 is running task4-1 @4times ······ @@TaskExecutor1-5 is running task4-1 @5times @@TaskExecutor2-5 is running task4-2 @5times @@TaskExecutor2-2 task4-2 is finished. @@TaskExecutor2-2 is running task4-2 @0times @@TaskExecutor1-2 task4-1 is finished. @@TaskExecutor1-2 is running task4-1 @0times @@TaskExecutor1-4 is running task4-1 @7times @@TaskExecutor2-4 is running task4-2 @7times @@TaskExecutor1-1 is running task4-1 @2times
以上
本例中的所有代码已经上传至github
(window.slotbydup = window.slotbydup || []).push({ id: "u5894387", container: "_0hv0l6ey3zro", async: true });- Spring的定时任务(任务调度)<task:scheduled-tasks>
- Spring3.0.6定时任务task:scheduled
- Spring3.0.6定时任务task:scheduled
- 定时任务之Spring中@Scheduled cron表达式
- 摆脱Spring 定时任务的@Scheduled cron表达式的困扰
- 使用spring @Scheduled注解执行定时任务
- spring的Scheduled(定时任务)和多线程
- Spring的两种任务调度Scheduled和Async
- 使用spring @Scheduled注解执行定时任务
- Spring 定时任务之 @Scheduled cron表达式
- 定时任务实现Timer, TimeTask, ScheduledExecutorService及Spring定时器
- Spring 定时任务之 @Scheduled cron表达式
- Spring ScheduledTimerTask 定时任务执行
- 使用spring @Scheduled注解执行定时任务、
- 集群服务器下使用SpringBoot @Scheduled注解定时任务
- 使用spring @Scheduled注解执行定时任务、
- springboot+scheduled定时任务
- Spring @SCHEDULED(CRON = "0 0 * * * ?")实现定时任务
- 使用spring @Scheduled注解执行定时任务
- 集群服务器下使用SpringBoot @Scheduled注解定时任务