springboot中@Scheduled 和@Async的使用

  • Post author:
  • Post category:其他


如题,今天在知乎突然看到一份关于springboot自带调度器的问题思考,有这么一段内容“在使用@Scheduled注解时,如果不自己重新配置调度器,那么就会使用默认的,从而会导致一些调度执行上的问题”;联系到自己在程序中使用时没有关注到这个问题,因此仔细测试研究一番,最终了解了其中的一些关键思想。

首先,需要了解@Scheduled 和@Async这俩注解的区别:

@Scheduled 任务调度注解,主要用于配置定时任务;springboot默认的调度器线程池大小为 1。

@Async 任务异步执行注解,主要用于方法上,表示当前方法会使用新线程异步执行;springboot默认执行器线程池大小为100。

具体可参考源码和这两篇博客(里面有些描述感觉有点问题,但不影响核心思想的表达,大家可参考看下):

https://blog.csdn.net/hello__ZC/article/details/102455847

https://blog.csdn.net/weixin_34319374/article/details/88811716

所以,如果在使用springboot定时器时,如果有多个定时任务时,在使用默认的调度器配置,就会出现排队现象,因为同时只能有一个任务在执行,这个时候当一个任务挂死,那后面的定时任务就不能有效执行了;

解决办法就是自定义调度器,有两种方式:

方法一:

  @Bean
    public TaskScheduler scheduledExecutorService() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-thread-");
        //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        scheduler.setAwaitTerminationSeconds(60);
        //这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return scheduler;
    }

方法二:

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {

	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
	taskRegistrar.setScheduler(setExecutor());
	}
	
	@Bean(destroyMethod="shutdown")
	public Executor setExecutor(){
	return Executors.newScheduledThreadPool(10); // 10个线程来处理。
	}
}

上述自定义调度器的方式,会有一个问题:当有足够的空余线程时,多任务时并行执行,但是同一定时任务仍会同步执行(当定时任务的执行时间大于每次执行的时间间隔时即可发现);

配合@Async 注解使用,这样在每次执行定时任务时就新开一个线程,异步非阻塞运行;同时使用这两个注解的效果,相当于@Scheduled仅仅负责调度,而@Async指定的executor负责任务执行,不再使用调度器中的执行器来执行任务(由实际测试结果来猜测的,并没有找到对应的源码逻辑,待后续补充)。

自定义执行器配置如下:

   @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);

        // 线程池对拒绝任务的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }

其余注意事项,可参考如下两份博客:

https://blog.csdn.net/hello__ZC/article/details/102455847

https://blog.csdn.net/dviewer/article/details/78022865



版权声明:本文为huangyaa729原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。