1、概序

默认使用单线程模式

2、单线程执行定时任务

@Configuration
@EnableScheduling
public class TestScheduled {
    private Logger logger = LoggerFactory.getLogger(TestScheduled.class);

    private int fixedDelayCount = 1;
    private int fixedRateCount = 1;
    private int initialDelayCount = 1;
    private int cronCount = 1;

    /**
     * fixedDelay = 5000表示当前方法执行完毕5000ms后,Spring scheduling会再次调用该方法
     */
    @Scheduled(fixedDelay = 5000)
    public void testFixedDelay() {
        logger.info("===fixedDelay: 第{}次执行方法", fixedDelayCount++);
    }

    /**
     * fixedRate = 5000表示当前方法开始执行5000ms后,Spring scheduling会再次调用该方法
     */
    @Scheduled(fixedRate = 5000)
    public void testFixedRate() {
        logger.info("===fixedRate: 第{}次执行方法", fixedRateCount++);
    }

    /**
     * initialDelay = 1000表示延迟1000ms执行第一次任务
     */
    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void testInitialDelay() {
        logger.info("===initialDelay: 第{}次执行方法", initialDelayCount++);
    }

    /**
     * cron接受cron表达式,根据cron表达式确定定时规则
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void testCron() {
        logger.info("===initialDelay: 第{}次执行方法", cronCount++);
    }

}

输出

2021-01-20 15:32:48.761  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第1次执行方法
2021-01-20 15:32:48.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第1次执行方法
2021-01-20 15:32:48.768  INFO 2520 --- [           main] com.hubz.scheduled.ScheduledApplication  : Started ScheduledApplication in 3.157 seconds (JVM running for 7.537)
2021-01-20 15:32:49.761  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第1次执行方法
2021-01-20 15:32:53.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第2次执行方法
2021-01-20 15:32:53.765  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第2次执行方法
2021-01-20 15:32:54.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第2次执行方法
2021-01-20 15:32:58.762  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第3次执行方法
2021-01-20 15:32:58.765  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第3次执行方法
2021-01-20 15:32:59.762  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第3次执行方法
2021-01-20 15:33:00.001  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第1次执行方法
2021-01-20 15:33:03.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第4次执行方法
2021-01-20 15:33:03.766  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第4次执行方法
2021-01-20 15:33:04.761  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第4次执行方法
2021-01-20 15:33:08.762  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第5次执行方法
2021-01-20 15:33:08.766  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第5次执行方法
2021-01-20 15:33:09.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第5次执行方法
2021-01-20 15:33:13.763  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第6次执行方法
2021-01-20 15:33:13.768  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第6次执行方法
2021-01-20 15:33:14.762  INFO 2520 --- [   scheduling-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第6次执行方法

分析: 从结果可以看出每次运行时都是使用的同一个线程,但是任务之间的耗时并不发生干扰,轮询执行并轮询结果

3、@Scheduled参数

使用 @Scheduled来创建定时任务 这个注解用来标注一个定时任务方法。
通过看 @Scheduled源码可以看出它支持多种参数:

  • cron:cron表达式,指定任务在特定时间执行;
  • fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
  • fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
  • fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
  • fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
  • initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
  • initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
  • zone:时区,默认为当前时区,一般没有用到。
@Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行;
 
@Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行;
 
@Scheduled(initialDelay=1000, fixedDelay=2000):第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行;
 
@Scheduled(cron="* * * * * ?"):按cron规则执行。

4、多线程执行定时任务

看到控制台输出的结果,所有的定时任务都是通过一个线程来处理的,我估计是在定时任务的配置中设定了一个SingleThreadScheduledExecutor,于是我看了源码,从ScheduledAnnotationBeanPostProcessor类开始一路找下去。果然,在ScheduledTaskRegistrar(定时任务注册类)中的ScheduleTasks中又这样一段判断:

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

这就说明如果taskScheduler为空,那么就给定时任务做了一个单线程的线程池,正好在这个类中还有一个设置taskScheduler的方法:

public void setScheduler(@Nullable Object scheduler) {
    if (scheduler == null) {
        this.taskScheduler = null;
    } else if (scheduler instanceof TaskScheduler) {
        this.taskScheduler = (TaskScheduler) scheduler;
    } else if (scheduler instanceof ScheduledExecutorService) {
        this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
    } else {
        throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
    }
}

这样问题就很简单了,我们只需用调用这个方法显式的设置一个ScheduledExecutorService就可以达到并发的效果了。我们要做的仅仅是实现SchedulingConfigurer接口,重写configureTasks方法就OK了;

/**
 * @author hubozhi
 * @desc 多线程执行定时任务
 * @datetime 2021/1/20 15:46
 **/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        //所有的定时任务都放在一个线程池中,定时任务启动时使用不同都线程。
        //设定一个长度10的定时任务线程池
        taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10));
    }
}

换一种写法(更好):

@Configuration
public class ScheduleConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        //官方建议1-10
        scheduler.setPoolSize(10);
        return scheduler;
    }
}

结果

2021-01-20 15:49:07.278  INFO 16904 --- [pool-1-thread-3] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第1次执行方法
2021-01-20 15:49:09.276  INFO 16904 --- [pool-1-thread-2] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第1次执行方法
2021-01-20 15:49:10.278  INFO 16904 --- [pool-1-thread-4] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第2次执行方法
2021-01-20 15:49:10.278  INFO 16904 --- [pool-1-thread-1] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第2次执行方法
2021-01-20 15:49:13.278  INFO 16904 --- [pool-1-thread-3] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第3次执行方法
2021-01-20 15:49:14.276  INFO 16904 --- [pool-1-thread-2] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第3次执行方法
2021-01-20 15:49:16.278  INFO 16904 --- [pool-1-thread-5] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第4次执行方法
2021-01-20 15:49:17.276  INFO 16904 --- [pool-1-thread-7] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第2次执行方法
2021-01-20 15:49:18.276  INFO 16904 --- [pool-1-thread-6] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第4次执行方法
2021-01-20 15:49:19.278  INFO 16904 --- [pool-1-thread-4] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第5次执行方法
2021-01-20 15:49:22.278  INFO 16904 --- [pool-1-thread-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第5次执行方法
2021-01-20 15:49:22.278  INFO 16904 --- [pool-1-thread-8] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第6次执行方法
2021-01-20 15:49:25.278  INFO 16904 --- [pool-1-thread-8] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第7次执行方法
2021-01-20 15:49:25.279  INFO 16904 --- [pool-1-thread-9] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第3次执行方法
2021-01-20 15:49:26.277  INFO 16904 --- [pool-1-thread-1] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第6次执行方法
2021-01-20 15:49:28.277  INFO 16904 --- [pool-1-thread-5] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第8次执行方法
2021-01-20 15:49:30.276  INFO 16904 --- [pool-1-thread-7] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第7次执行方法
2021-01-20 15:49:31.278  INFO 16904 --- [pool-1-thread-4] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第9次执行方法
2021-01-20 15:49:33.281  INFO 16904 --- [pool-1-thread-6] com.hubz.scheduled.task.TestScheduled    : ===fixedDelay: 第4次执行方法
2021-01-20 15:49:34.277  INFO 16904 --- [pool-1-thread-3] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第8次执行方法
2021-01-20 15:49:34.277  INFO 16904 --- [ool-1-thread-10] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第10次执行方法
2021-01-20 15:49:37.276  INFO 16904 --- [ool-1-thread-10] com.hubz.scheduled.task.TestScheduled    : ===initialDelay: 第11次执行方法
2021-01-20 15:49:38.277  INFO 16904 --- [pool-1-thread-3] com.hubz.scheduled.task.TestScheduled    : ===fixedRate: 第9次执行方法