• springboot定时器(一)


    前言

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

    问题

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

    验证流程

    1. 在项目启动时发现初始化taskSchedulerasyncTaskExecutor两个,但是我自定义了一个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'
    
    
    1. 查找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.
    
    

    这时我们发现taskSchedulerTaskSchedulingAutoConfiguration类中初始化,再进入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
    
    

    总结

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

    博客地址:

    项目GitHub地址:

  • 相关阅读:
    Sublime Text3 包管理器、插件安装
    Sublime text3 安装
    VS中的波浪线
    VS的启动方式
    VS常用快捷键
    C#基础性问题
    nginx前端项目发布
    vue父子组件实现数据双向绑定
    常用在线echarts图表
    使用echarts地图踩坑记
  • 原文地址:https://www.cnblogs.com/guoyuchuan/p/12951715.html
Copyright © 2020-2023  润新知