排查定时任务为什么不执行

首先看源码注释

/**
 * Annotation that marks a method to be scheduled. Exactly one of the
 * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes must be
 * specified.
 * 
 * 将一个方法标记为定时任务的注解。必须明确地指定cron,fixedDelay或fixedRate属性
 * 
 * 
 * <p>The annotated method must expect no arguments. It will typically have
 * a {@code void} return type; if not, the returned value will be ignored
 * when called through the scheduler.
 *
 * 带注解的方法不能有参数。返回值类型必须是void;否则,调度器调用时会忽略返回值。
 *
 *
 * <p>Processing of {@code @Scheduled} annotations is performed by
 * registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
 * done manually or, more conveniently, through the {@code <task:annotation-driven/>}
 * element or @{@link EnableScheduling} annotation.
 *
 * 通过注册到ScheduledAnnotationBeanPostProcessor后处理bean中来执行注解标注的程序。可以手动将定时任务注册到ScheduledAnnotationBeanPostProcessor后处理bean中,或者通过<task:annotation-driven/>标签和@EnableScheduling注解。
 *
 *
 * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
 * <em>composed annotations</em> with attribute overrides.
 *
 * 看不懂(可以作为元注解创建自定义注解?)
 *
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Dave Syer
 * @author Chris Beams
 * @since 3.0
 * @see EnableScheduling
 * @see ScheduledAnnotationBeanPostProcessor
 * @see Schedules
 */

注释里说@Scheduled注解的方法被注册到ScheduledAnnotationBeanPostProcessor后处理Bean里面了,来看看ScheduledAnnotationBeanPostProcessor源码

public class ScheduledAnnotationBeanPostProcessor
		implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
		Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
		SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {



	/**
	 * The default name of the {@link TaskScheduler} bean to pick up: {@value}.
	 * <p>Note that the initial lookup happens by type; this is just the fallback
	 * in case of multiple scheduler beans found in the context.
	 * @since 4.2
	 */
	public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";


	private final ScheduledTaskRegistrar registrar;

	@Nullable
	private Object scheduler;

	/**
	 * Create a default {@code ScheduledAnnotationBeanPostProcessor}.
	 */
	public ScheduledAnnotationBeanPostProcessor() {
		this.registrar = new ScheduledTaskRegistrar();
	}

	/**
	 * Create a {@code ScheduledAnnotationBeanPostProcessor} delegating to the
	 * specified {@link ScheduledTaskRegistrar}.
	 * @param registrar the ScheduledTaskRegistrar to register @Scheduled tasks on
	 * @since 5.1
	 */
	public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) {
		Assert.notNull(registrar, "ScheduledTaskRegistrar is required");
		this.registrar = registrar;
	}


	/**
	 * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
	 * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
	 * to be wrapped as a TaskScheduler.
	 * 
	 * 设置被调用的方法为TaskScheduler,或将ScheduledExecutorService包装成TaskScheduler。
	 * 
	 * 
	 * <p>If not specified, default scheduler resolution will apply: searching for a
	 * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}
	 * bean named "taskScheduler" otherwise; the same lookup will also be performed for
	 * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,
	 * a local single-threaded default scheduler will be created within the registrar.
	 * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME
	 * 
	 * 设置被调用的方法为TaskScheduler,或将ScheduledExecutorService包装成TaskScheduler。
	 * 如果没有指定,将使用以下两个默认方案:
	 * 一、在容器里查找唯一的TaskScheduler实例。
	 * 二、在容器里查找名字是taskScheduler的实例。
	 * 如果两个方案都无法解析,将会创建一个单线程默认调度器。
	 * 
	 * ???
	 * 
	 */
	public void setScheduler(Object scheduler) {
		this.scheduler = scheduler;
	}
}

意味着, 如果不配置TaskScheduler, SpringBoot会默认使用一个单线程的scheduler来处理@Scheduled注解标注的定时任务。

发现项目中有一个定时任务延迟6秒启动,然后死循环。(@Scheduled注解各参数详解)

    /**
     * 延迟60秒继续执行,异步执行;
     */
    @Scheduled(fixedDelay = 6000)
    public void run() {
        log.info("init zk register");

        while (true) {

            log.info("do something...");

            Thread.sleep(5000);
        }
    }

由此断定是因为线程池里唯一的线程被死循环占用了。

解决方案:

一:注册zk的线程修改为5秒执行一次的定时任务,不睡眠。

    /**
     * 延迟60秒继续执行,异步执行;
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void run() {

        log.info("init zk register");

        log.info("do something...");

    }

二:主动配置TaskScheduler,指定线程池线程数量。

package com.soyoung.ztdata.dataservice.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

/**
 * @program: zt_service
 * @description:
 * @author: Leon
 * @create: 2021-06-04 22:07
 **/
@Configuration
public class SoYongSchedulingConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(2);
        threadPoolTaskScheduler.initialize();
        taskRegistrar.setScheduler(threadPoolTaskScheduler);
    }
}

三:修改配置文件,指定定时任务线程池线程数量。

spring.task.scheduling.pool.size=2

 

上一篇:SpringBoot集成Quartz动态创建、启动、暂停、恢复、删除定时任务,实现定时任务的管理


下一篇:Oracle数据库——Scheduler Job