SpringBoot自带的Scheduled,可以将它看成一个轻量级的Quartz,默认情况下是单线程的,也就是无论同时有多少个任务需要执行,都需要排队等待某个任务完成之后才能继续下一个任务。
所以,如果在使用springboot定时器时,如果有多个定时任务时,在使用默认的调度器配置,就会出现排队现象,因为同时只能有一个任务在执行,这个时候当一个任务挂死,那后面的定时任务就不能有效执行了;
解决办法就是自定义调度器,有两种方式:
方法1:
@Bean
public TaskScheduler scheduledExecutorService() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-thread-");
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
scheduler.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
scheduler.setAwaitTerminationSeconds(60);
//这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return scheduler;
}
方法二:
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(setExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor setExecutor(){
return Executors.newScheduledThreadPool(10); // 10个线程来处理。
}
}
上述自定义调度器的方式,会有一个问题:当有足够的空余线程时,多任务时并行执行,但是同一定时任务仍会同步执行(当定时任务的执行时间大于每次执行的时间间隔时即可发现)。
我们要用到@Async来设置任务为异步执行。
首先,需要了解@Scheduled 和@Async这俩注解的区别:
@Scheduled 任务调度注解,主要用于配置定时任务;springboot默认的调度器线程池大小为 1。
@Async 任务异步执行注解,主要用于方法上,表示当前方法会使用新线程异步执行;springboot默认执行器线程池大小为100。
配合@Async 注解使用,这样在每次执行定时任务时就新开一个线程,异步非阻塞运行;同时使用这两个注解的效果,相当于@Scheduled仅仅负责调度,而@Async指定的executor负责任务执行,不再使用调度器中的执行器来执行任务。
具体的例子可参考源码和这两篇博客(大家可参考看下):
https://blog.youkuaiyun.com/hello__ZC/article/details/102455847
https://blog.youkuaiyun.com/weixin_34319374/article/details/88811716
官方文档:https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/scheduling.html