SpringBoot集成任务调度Scheduler

基础步骤

  • 第一步:引入pom依赖
<!--支持任务调度-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
  • 第二步:开启任务调度支持
@EnableScheduling
public class RootConfig {
}
  • 第三步:自定义类
@Component
public class ScheduleJob {
    /**
     * 每间隔两秒执行一次
     * fixedDelay: 间隔时间是根据上次的任务结束的时候开始计时的
     *      2022-05-07T22:01:53.025288100----------
     *      2022-05-07T22:02:00.027980700----------
     *      2022-05-07T22:02:07.031691200----------
     * fixedRate: 间隔时间是根据上次任务开始的时候计时的   易产生线程阻塞
     *      2022-05-07T22:05:01.320040600----------
     *      2022-05-07T22:05:06.320986600----------
     *      2022-05-07T22:05:11.321895300----------
     */
//    @Scheduled(fixedDelay = 2,timeUnit = TimeUnit.SECONDS)
    @Scheduled(fixedRate = 2,timeUnit = TimeUnit.SECONDS)
    public void testSchedule() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        System.out.println(LocalDateTime.now()+"----------");
    }
}

Scheduled注解

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    //cron表达式
    String cron() default "";
	//单位默认毫秒
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

@Scheduled(fixedRate)如何避免任务被阻塞

加上注解@EnableAsync(类上)和@Async(方法上),加了注解以后,就开启了多线程模式,当到了下一次任务的执行时机时,如果上一次任务还没执行完,就会自动创建一个新的线程来执行它。异步执行也可以理解为保证了任务以固定速度执行。

开启多线程后,每次任务开始的间隔都是5秒钟。这是符合我们预期的,但是最后还有点缺陷,这种情况下的线程是随着任务一执行完就销毁的,等下次有需要了程序再创建一个。每次都要重新创建明显是太影响性能了,所以需要在代码里给他一个线程池。

  • 创建线程池
@Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }

解决线程阻塞问题

@Component
@EnableAsync
public class ScheduleJob {
    
    @Scheduled(fixedRate = 2,timeUnit = TimeUnit.SECONDS)
    @Async
    public void testSchedule() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        System.out.println(LocalDateTime.now()+"----------");
    }
}

执行结果如下

2022-05-07T22:20:48.117983200----------
2022-05-07T22:20:50.072179500----------
2022-05-07T22:20:52.067858600----------
2022-05-07T22:20:54.067964400----------
2022-05-07T22:20:56.067006800----------

fixedRate和fixedDelay的区别:

fixedDelay非常好理解,它的间隔时间是根据上次的任务结束的时候开始计时的。比如一个方法上设置了fixedDelay=5*1000,那么当该方法某一次执行结束后,开始计算时间,当时间达到5秒,就开始再次执行该方法。

fixedRate理解起来比较麻烦,它的间隔时间是根据上次任务开始的时候计时的。比如当方法上设置了fiexdRate=51000,该执行该方法所花的时间是2秒,那么3秒后就会再次执行该方法。
但是这里有个坑,当任务执行时长超过设置的间隔时长,那会是什么结果呢。打个比方,比如一个任务本来只需要花2秒就能执行完成,我所设置的fixedRate=5
1000,但是因为网络问题导致这个任务花了7秒才执行完成。当任务开始时Spring就会给这个任务计时,5秒钟时候Spring就会再次调用这个任务,可是发现原来的任务还在执行,这个时候第二个任务就阻塞了(这里只考虑单线程的情况下),甚至如果第一个任务花费的时间过长,还可能会使第三第四个任务被阻塞。被阻塞的任务就像排队的人一样,一旦前一个任务没了,它就立马执行。

cron表达式

通配符

单位

符号支持


, - * /


, - * /


, - * /


, - * / L W


, - * /

星期/周

, - * / L # 只有 # 代表 周, 其他代表 星期

, :表示枚举,例如1,2,3

-:标识连续,例如3-5

*: 表示 每一秒、分…

/:表示 间隔 , 例如 1/3从1秒开始每间隔3秒

L:表示最后, 例如 L

W:指定日最接近的工作日, 例如 2W

#: 指定第几周的星期几,例如 3#2
只有 # 代表 周, 其他代表 星期

?:日 或 星期 中 可以使用 ,表示 不指定

/**
     * 该任务在5月的 每一天 22点  每一分的   1,2,3秒时执行
     * 秒   分   时  日   月   星期/周
     * @throws InterruptedException
     */
    @Scheduled(cron = "1,2,3 * 22 * 5 ?")
    public void testSchedule2() throws InterruptedException {
        System.err.println(LocalDateTime.now()+"===========");
    }

多线程

/**
     * 多线程   每秒执行一次任务
     * @throws InterruptedException
     */
    @Scheduled(cron = "* * * * * ?")
    public void testSchedule3() throws InterruptedException {
        System.err.println(Thread.currentThread()+"-"+LocalDateTime.now()+"\\\\\\\\");
    }
  • yml创建线程池数量
spring:
  task:
    scheduling:
      pool:
        size: 5

官方参考文档

https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support