初探Spring Scheduler

项目使用很多@Scheduled(cron=**)注解来实现定时任务,部署到生产环境跑了一段时间,发现某些任务未按cron表达式定义的频率执行。

怀疑

初步怀疑未按频率执行原因可能是频率设置不合理,任务执行时间较长导致,优化部分程序执行时间后,有所改善。后来又发现,所有任务都不执行了,经排查发现某个定时任务阻塞在Socket读操作上。但某一个任务阻塞应该不会影响其他任务啊,事实却是所有都阻塞,这下就怀疑是不是Spring定时任务有问题?

我们臆测Spring会开多个线程执行定时任务,但实际情况是不是这样呢?

源码

带着这些疑问,我一步一步阅读Spring 3.1.0.RELEASE源码:

Listener,配置在web.xml,容器启动时调用

org.springframework.web.context.ContextLoaderListener.contextInitialized(ServletContextEvent)

实例化WebApplicationContext

org.springframework.web.context.ContextLoader.initWebApplicationContext(ServletContext)

新建WebApplicationContext,如果未指定,默认新建XmlWebApplicationContext

org.springframework.web.context.ContextLoader.createWebApplicationContext(ServletContext)

org.springframework.web.context.support.XmlWebApplicationContext

XmlWebApplicationContext是ConfigurableWebApplicationContext,配置并刷新WebApplicationContext,最后调用ConfigurableApplicationContext的refresh方法。

org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext, ServletContext)

org.springframework.context.ConfigurableApplicationContext.refresh()

抽象类AbstractApplicationContext实现refresh这一重要逻辑。

public void refresh() throws BeansException,IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

ScheduledAnnotationBeanPostProcessor是@Scheduled注解处理类,实现BeanPostProcessor接口(postProcessAfterInitialization方法实现注解扫描和类实例创建)、ApplicationContextAware接口(setApplicationContext方法设置当前ApplicationContext)、org.springframework.context.ApplicationListener(观察者模式,onApplicationEvent方法会被回调)。

ScheduledAnnotationBeanPostProcessor postProcessAfterInitialization扫描所有@Scheduled注解,区分cronTasks、fixedDelayTasks、fixedRateTasks

    public Object postProcessAfterInitialization(final Object bean, String beanName) {
        final Class<?> targetClass = AopUtils.getTargetClass(bean);
        ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                Scheduled annotation = AnnotationUtils.getAnnotation(method, Scheduled.class);
                if (annotation != null) {
                    Assert.isTrue(void.class.equals(method.getReturnType()),
                            "Only void-returning methods may be annotated with @Scheduled.");
                    Assert.isTrue(method.getParameterTypes().length == 0,
                            "Only no-arg methods may be annotated with @Scheduled.");
                    if (AopUtils.isJdkDynamicProxy(bean)) {
                        try {
                            // found a @Scheduled method on the target class for this JDK proxy -> is it
                            // also present on the proxy itself?
                            method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                        } catch (SecurityException ex) {
                            ReflectionUtils.handleReflectionException(ex);
                        } catch (NoSuchMethodException ex) {
                            throw new IllegalStateException(String.format(
                                    "@Scheduled method '%s' found on bean target class '%s', " +
                                    "but not found in any interface(s) for bean JDK proxy. Either " +
                                    "pull the method up to an interface or switch to subclass (CGLIB) " +
                                    "proxies by setting proxy-target-class/proxyTargetClass " +
                                    "attribute to 'true'", method.getName(), targetClass.getSimpleName()));
                        }
                    }
                    Runnable runnable = new ScheduledMethodRunnable(bean, method);
                    boolean processedSchedule = false;
                    String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required.";
                    String cron = annotation.cron();
                    if (!"".equals(cron)) {
                        processedSchedule = true;
                        if (embeddedValueResolver != null) {
                            cron = embeddedValueResolver.resolveStringValue(cron);
                        }
                        cronTasks.put(runnable, cron);
                    }
                    long fixedDelay = annotation.fixedDelay();
                    if (fixedDelay >= 0) {
                        Assert.isTrue(!processedSchedule, errorMessage);
                        processedSchedule = true;
                        fixedDelayTasks.put(runnable, fixedDelay);
                    }
                    long fixedRate = annotation.fixedRate();
                    if (fixedRate >= 0) {
                        Assert.isTrue(!processedSchedule, errorMessage);
                        processedSchedule = true;
                        fixedRateTasks.put(runnable, fixedRate);
                    }
                    Assert.isTrue(processedSchedule, errorMessage);
                }
            }
        });
        return bean;
    }

finishRefresh方法触发所有监视者方法回调

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext() != this.applicationContext) {
            return;
        }

        Map<String, SchedulingConfigurer> configurers = applicationContext.getBeansOfType(SchedulingConfigurer.class);

        if (this.cronTasks.isEmpty() && this.fixedDelayTasks.isEmpty() &&
                this.fixedRateTasks.isEmpty() && configurers.isEmpty()) {
            return;
        }

        this.registrar = new ScheduledTaskRegistrar();
        this.registrar.setCronTasks(this.cronTasks);
        this.registrar.setFixedDelayTasks(this.fixedDelayTasks);
        this.registrar.setFixedRateTasks(this.fixedRateTasks);

        if (this.scheduler != null) {
            this.registrar.setScheduler(this.scheduler);
        }

        for (SchedulingConfigurer configurer : configurers.values()) {
            configurer.configureTasks(this.registrar);
        }

        if (registrar.getScheduler() == null) {
            Map<String, ? super Object> schedulers = new HashMap<String, Object>();
            schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
            schedulers.putAll(applicationContext.getBeansOfType(ScheduledExecutorService.class));
            if (schedulers.size() == 0) {
                // do nothing -> fall back to default scheduler
            } else if (schedulers.size() == 1) {
                this.registrar.setScheduler(schedulers.values().iterator().next());
            } else if (schedulers.size() >= 2){
                throw new IllegalStateException("More than one TaskScheduler and/or ScheduledExecutorService  " +
                        "exist within the context. Remove all but one of the beans; or implement the " +
                        "SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler " +
                        "explicitly within the configureTasks() callback. Found the following beans: " + schedulers.keySet());
            }
        }

        this.registrar.afterPropertiesSet();
    }

onApplicationEvent方法最后调用

    public void afterPropertiesSet() {
        //关键是这里,项目如果只使用@Scheduled注解,未配置TaskScheduler,Spring只会实例化一个线程的线程池
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        if (this.triggerTasks != null) {
            for (Map.Entry<Runnable, Trigger> entry : this.triggerTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), entry.getValue()));
            }
        }
        if (this.cronTasks != null) {
            for (Map.Entry<Runnable, String> entry : this.cronTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), new CronTrigger(entry.getValue())));
            }
        }
        if (this.fixedRateTasks != null) {
            for (Map.Entry<Runnable, Long> entry : this.fixedRateTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(entry.getKey(), entry.getValue()));
            }
        }
        if (this.fixedDelayTasks != null) {
            for (Map.Entry<Runnable, Long> entry : this.fixedDelayTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(entry.getKey(), entry.getValue()));
            }
        }
    }

从关键点代码可以看出,Spring实例化只有一个线程的定时任务线程池newSingleThreadScheduledExecutor

if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }

结论

  1. 全采用@Scheduled(cron=**)注解,不单独配置TaskScheduler,Spring只会开一个线程执行所有定时任务。
  2. 如果配置多个TaskScheduler,没有给@Scheduled(cron=**)注解指定某个TaskScheduler,运行会报错。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值