Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

配置SpringBoot定时任务线程池 #21

Open
jwenjian opened this issue May 26, 2019 · 0 comments
Open

配置SpringBoot定时任务线程池 #21

jwenjian opened this issue May 26, 2019 · 0 comments

Comments

@jwenjian
Copy link
Owner

文章迁移自Hexo

在SpringBoot中可以通过@Scheduled 注解来定义一个定时任务, 下面这段代码定义了一个每隔十秒钟执行一次的定时任务:

@Component
public class ScheduledTaskDemo {
    private static final Logger logger = LoggerFactory.getLogger(ScheduledTaskDemo.class);

    @Scheduled(cron = "0/10 * * * * *")
    public void execute() {
        logger.info("Scheduled task is running... ...");
    }
}

此时启动SpringBoot应用, 可以在控制台看到这个定时任务每隔10秒钟打印一条log

2017-12-21 22:20:20.832 INFO 7424 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8000 (http)
2017-12-21 22:20:20.859 INFO 7424 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 12.134 seconds (JVM running for 14.156)
2017-12-21 22:20:30.002 INFO 7424 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : Scheduled task is running... ...
2017-12-21 22:20:40.001 INFO 7424 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : Scheduled task is running... ...
2017-12-21 22:20:50.002 INFO 7424 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : Scheduled task is running... ...

但是, 一切还没结束.

如果没有相关log显示, 检查是否在入口类或者Configuration类上添加了@EnableScheduling注解

在上面的相关代码中, 我们使用cron表达式来指定定时任务的执行时间点, 即从0秒开始, 每隔10秒钟执行一次, 现在我们再加一个定时任务:

@Component
public class SecondScheduledTaskDemo {
    private static final Logger logger = LoggerFactory.getLogger(ScheduledTaskDemo.class);

    @Scheduled(cron = "0/10 * * * * *")
    public void first() {
        logger.info("Second scheduled task is starting... ...");
        logger.info("Second scheduled task is ending... ...");
    }

}

现在再启动SpringBoot应用, 再看log:

2017-12-21 23:12:20.007 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is starting... ...
2017-12-21 23:12:35.013 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is ending... ...
2017-12-21 23:12:35.016 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.SecondScheduledTaskDemo : Second scheduled task is starting... ...
2017-12-21 23:12:35.016 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.SecondScheduledTaskDemo : Second scheduled task is ending... ...
2017-12-21 23:12:40.000 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is starting... ...
2017-12-21 23:12:55.001 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is ending... ...
2017-12-21 23:12:55.002 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.SecondScheduledTaskDemo : Second scheduled task is starting... ...
2017-12-21 23:12:55.002 INFO 13208 --- [pool-1-thread-1] c.e.demo.scheduled.SecondScheduledTaskDemo : Second scheduled task is ending... ...

注意log中定时任务执行的时间点, 第二个定时任务原本应该每隔10秒钟执行一次, 但是从23:12:20到23:13:55, 本该执行4次, 确只执行了2次.

难道是cron表达式不对?

No.

为了找到原因, 我们从@Scheduled注解的源码开始找:

 *
 * <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.
 *

划重点, 每一个有@Scheduled注解的方法都会被注册为一个ScheduledAnnotationBeanPostProcessor, 再接着往下看ScheduledAnnotationBeanPostProcessor:

	/**
	 * 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.
	 * <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
	 */
	public void setScheduler(Object scheduler) {
		this.scheduler = scheduler;
	}

重点来了, 注意这句话:

If neither of the two is resolvable, a local single-threaded default scheduler will be created within the registrar.

这句话意味着, 如果我们不主动配置我们需要的TaskScheduler, SpringBoot会默认使用一个单线程的scheduler来处理我们用@Scheduled注解实现的定时任务, 到此我们刚才的问题就可以理解了:

23:12:20, 第一个定时任务在线程pool-1-thread-1开始执行, 由于我们没有配置scheduler, 目前这个线程池pool-1里只有一个线程, 在打印了starting日志之后, 这个线程开始sleep;第二个定时任务也准备执行, 但是线程池已经没有多余线程了, 只能等待.

23:12:30, 第一个定时任务还在sleep, 第二个定时任务还在等待.

23:12:35, 第一个定时任务sleep结束, 打印ending日志并结束, 此时线程池空闲, 第二个定时任务从等待状态直接开始执行, 执行结束之后, 线程池空闲.

23:12:40, 线程池空闲, 第一个定时任务执行, 打印starting日志, 开始sleep.

注意, 这里也有可能执行第二个定时任务, 看CPU了.

... ...

搞清楚这个流程之后, 解决这个问题就很简单了.

根据刚才注释的描述, 我们只需要提供一个满足我们需要的TaskScheduler并注册到context中就可以了.

@Configuration
public class ScheduledTaskConfiguration implements SchedulingConfigurer {

    /**
     * Callback allowing a {@link TaskScheduler
     * TaskScheduler} and specific {@link Task Task}
     * instances to be registered against the given the {@link ScheduledTaskRegistrar}
     *
     * @param taskRegistrar the registrar to be configured.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        final ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(2);
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

上面的代码提供了一个线程池大小为2的taskScheduler, 现在再启动下SpringBoot看看效果.

2017-12-21 23:28:30.001 INFO 7972 --- [TaskScheduler-1] c.e.demo.scheduled.ScheduledTaskDemo : Second scheduled task is starting... ...
2017-12-21 23:28:30.001 INFO 7972 --- [TaskScheduler-2] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is starting... ...
2017-12-21 23:28:30.002 INFO 7972 --- [TaskScheduler-1] c.e.demo.scheduled.ScheduledTaskDemo : Second scheduled task is ending... ...
2017-12-21 23:28:40.001 INFO 7972 --- [TaskScheduler-1] c.e.demo.scheduled.ScheduledTaskDemo : Second scheduled task is starting... ...
2017-12-21 23:28:40.001 INFO 7972 --- [TaskScheduler-1] c.e.demo.scheduled.ScheduledTaskDemo : Second scheduled task is ending... ...
2017-12-21 23:28:45.002 INFO 7972 --- [TaskScheduler-2] c.e.demo.scheduled.ScheduledTaskDemo : First scheduled task is ending... ...

可以看到, 当线程池里有两个线程的时候, 这两个定时任务各自按照预定的时间进行触发, 互不影响了.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant