springboot定时器(一)

该图片由pics_kartub在Pixabay上发布

前言

本篇文章针对上篇文章springboot异步线程,有一位大佬在评论中提出第一点是错误的,当时看到了这个问题,最近刚好有空,针对第一点的问题去搜索了不少的文章;

问题

我在文章中第一点去验证:Scheduled为单线程执行,这是错误的;正确的是,scheduled单线程执行是因为使用默认线程池核心线程数为1,如果配置默认线程池ThreadPoolTaskScheduler的核心线程数,则一样是多线程的执行,这里直接贴出了大佬的原话。

验证流程

  1. 在项目启动时发现初始化taskSchedulerasyncTaskExecutor两个,但是我自定义了一个asyncTaskExecutor,那么另一个怎么回事呢;
1
2
3
4
5

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'

  1. 查找taskScheduler

根据SpringBoot源码解析-Scheduled定时器的原理这篇文章里面的springboot中定时器的原理找到ScheduledAnnotationBeanPostProcessor 类,该类只是实现原理,而且是通过BeanFactory来获取的taskScheduler,那taskScheduler在哪里初始化进容器的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

@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,会出现以下错误:

1
2
3
4
5

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.

这时我们发现taskSchedulerTaskSchedulingAutoConfiguration类中初始化,再进入TaskSchedulingAutoConfiguration类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

 @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中自定义参数:

1
2
3
4
5
6
7
8

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

再来看配置后的打印信息

1
2
3
4
5
6
7
8
9
10
11
12
13

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

总结

  1. Scheduled注解默认使用taskScheduler线程池
  2. taskScheduler线程池使用默认属性,也就是线程只有一个,所以才会误认为Scheduled为单线程
  3. 如果使用默认的线程池taskScheduler,如果不修改默认参数,在使用中,线程出现堵塞或死循环问题会造成定时任务无法定时或者不能执行;

博客地址:

项目GitHub地址:

坚持原创技术分享,您的支持将鼓励我继续创作!