Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说springboot定时器(一)「终于解决」,希望能够帮助你!!!。
本篇文章针对上篇文章springboot异步线程,有一位大佬在评论中提出第一点是错误的,当时看到了这个问题,最近刚好有空,针对第一点的问题去搜索了不少的文章;
我在文章中第一点去验证:Scheduled为单线程执行,这是错误的;正确的是,scheduled单线程执行是因为使用默认线程池核心线程数为1,如果配置默认线程池ThreadPoolTaskScheduler的核心线程数,则一样是多线程的执行,这里直接贴出了大佬的原话。
taskScheduler
和asyncTaskExecutor
两个,但是我自定义了一个asyncTaskExecutor
,那么另一个怎么回事呢;
2019-12-16 15:16:55.388 INFO 18736 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2019-12-16 15:16:55.389 INFO 18736 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'asyncTaskExecutor'
2019-12-16 15:16:55.560 INFO 18736 --- [main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
taskScheduler
根据SpringBoot源码解析-Scheduled定时器的原理这篇文章里面的springboot中定时器的原理找到ScheduledAnnotationBeanPostProcessor
类,该类只是实现原理,而且是通过BeanFactory
来获取的taskScheduler
,那taskScheduler
在哪里初始化进容器的呢?
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {
if (byName) {
T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(
DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
}
return scheduler;
}
else if (beanFactory instanceof AutowireCapableBeanFactory) {
NamedBeanHolder<T> holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
这里我用了一个本办法:将我上一篇文章的AsyncConfig
类中的bean改为:
@Bean
public AsyncTaskExecutor taskScheduler() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("courses-schedule-");
//最大线程数10:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
//核心线程数3:线程池创建时候初始化的线程数
executor.setCorePoolSize(3);
//缓冲队列0:用来缓冲执行任务的队列
executor.setQueueCapacity(5);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 当线程池已满,且等待队列也满了的时候,直接抛弃当前线程(不会抛出异常)
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
再次启动springboot,会出现以下错误:
Description:
The bean 'taskScheduler', defined in class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/example/async/config/AsyncConfig.class] and overriding is disabled.
这时我们发现taskScheduler
在TaskSchedulingAutoConfiguration
类中初始化,再进入TaskSchedulingAutoConfiguration
类:
@Bean
@ConditionalOnMissingBean
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
builder = builder.poolSize(properties.getPool().getSize());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskSchedulerCustomizers);
return builder;
}
发现该bean使用了TaskSchedulingProperties
,再进入TaskSchedulingProperties
类,发现该类上有@ConfigurationProperties("spring.task.scheduling")
注解,到这里就可以发现taskScheduler
是可以通过properties配置文件配置参数了,下边我们看一下taskScheduler
的默认参数吧:
TaskExecutionProperties类:
private int queueCapacity = 2147483647;
private int coreSize = 8;
private int maxSize = 2147483647;
private boolean allowCoreThreadTimeout = true;
private Duration keepAlive = Duration.ofSeconds(60L);
TaskSchedulingProperties类
private int size = 1;
private String threadNamePrefix = "scheduling-";
在properties中自定义参数:
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.execution.pool.max-size=10
spring.task.execution.pool.core-size=3
spring.task.execution.pool.queue-capacity=5
spring.task.scheduling.pool.size=10
spring.task.execution.pool.keep-alive=60s
再来看配置后的打印信息
2019-12-16 17:43:47.497 INFO 22476 --- [ main] com.example.async.AsyncApplication : Started AsyncApplication in 1.337 seconds (JVM running for 1.889)
2019-12-16 17:44:47.480 INFO 22476 --- [ scheduling-2] c.example.async.timetask.TestScheduling : ThreadName:====two====scheduling-2
2019-12-16 17:44:47.480 INFO 22476 --- [ scheduling-1] c.example.async.timetask.TestScheduling : ThreadName:====one====scheduling-1
2019-12-16 17:44:57.481 INFO 22476 --- [ scheduling-1] c.example.async.timetask.TestScheduling : ThreadName:====one====scheduling-1
2019-12-16 17:44:57.481 INFO 22476 --- [ scheduling-2] c.example.async.timetask.TestScheduling : ThreadName:====two====scheduling-2
2019-12-16 17:45:07.482 INFO 22476 --- [ scheduling-3] c.example.async.timetask.TestScheduling : ThreadName:====one====scheduling-3
2019-12-16 17:45:07.482 INFO 22476 --- [ scheduling-4] c.example.async.timetask.TestScheduling : ThreadName:====two====scheduling-4
2019-12-16 17:45:17.483 INFO 22476 --- [ scheduling-2] c.example.async.timetask.TestScheduling : ThreadName:====two====scheduling-2
2019-12-16 17:45:17.483 INFO 22476 --- [ scheduling-1] c.example.async.timetask.TestScheduling : ThreadName:====one====scheduling-1
2019-12-16 17:45:27.484 INFO 22476 --- [ scheduling-5] c.example.async.timetask.TestScheduling : ThreadName:====two====scheduling-5
2019-12-16 17:45:27.484 INFO 22476 --- [ scheduling-6] c.example.async.timetask.TestScheduling : ThreadName:====one====scheduling-6
Scheduled
注解默认使用taskScheduler
线程池taskScheduler
线程池使用默认属性,也就是线程只有一个,所以才会误认为Scheduled
为单线程taskScheduler
,如果不修改默认参数,在使用中,线程出现堵塞或死循环问题会造成定时任务无法定时或者不能执行;博客地址:
项目GitHub地址:
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。