SpringBoot实战(二)Spring内置的定时任务

文章目录

  • 原文链接
  • 1. 前言
  • 2. Spring中实现定时任务的三种方式
    • 2.1 Spring Task
      • 2.1.1 @EnableScheduling 开启定时任务
      • 2.1.2 @Scheduled 实现定时任务
      • 2.1.3 @Scheduled 中方法介绍
        • 2.1.3.1 cron 表达式
        • 2.1.3.2 fixedDelay
        • 2.1.3.3 fixedRate
        • 2.1.3.4 initialDelay
      • 2.1.4 cron表达式详解
      • 2.1.5 Spring Task 的弊端
    • 2.2 Timer
    • 2.3 ScheduledExecutorService

原文链接

https://cloud.tencent.com/developer/article/1582434

1. 前言

在日常项目开发中我们经常要使用定时任务。比如在凌晨进行统计结算,开启策划活动,自动将超过24小时的未付款的单改为取消状态,自动将超过14天客户未签收的订单改为已签收状态等等。今天我们就来看看如何在 Spring Boot 中使用 Spring 内置的定时任务。

2. Spring中实现定时任务的三种方式

2.1 Spring Task

Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

2.1.1 @EnableScheduling 开启定时任务

Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。我们只需要使用 @EnableScheduling 注解就可以开启相关的定时任务功能。如:

@SpringBootApplication
@EnableScheduling
public class SpringbootScheduleApplication {
    public static void main(String[] args) {
	SpringApplication.run(SpringbootScheduleApplication.class, args);
    }
}

然后我们就可以通过注解的方式实现自定义定时任务。

2.1.2 @Scheduled 实现定时任务

@Component
public class Task {

    @Scheduled(fixedDelay = 1000)
    public void task() {
        System.out.println("Thread Name : "
                + Thread.currentThread().getName() + "  i am a task : date ->  "
                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}

结果:
SpringBoot实战(二)Spring内置的定时任务_第1张图片
注意:@Scheduled 注解中一定要声明定时任务的执行策略 cronfixedDelayfixedRate 三选一。

2.1.3 @Scheduled 中方法介绍

2.1.3.1 cron 表达式

2.1.3.2 fixedDelay

fixedDelay: 它的间隔时间是根据上次的任务结束的时候开始计时的,只要盯紧上一次执行结束的时间即可,跟任务逻辑的执行时间无关,两个轮次的间隔距离是固定的。
SpringBoot实战(二)Spring内置的定时任务_第2张图片

2.1.3.3 fixedRate

fixedRate: 这个相对难以理解一些。在理想情况下,下一次开始和上一次开始之间的时间间隔是一定的。但是默认情况下 Spring Boot 定时任务是单线程执行的。当下一轮的任务满足时间策略后任务就会加入队列,也就是说当本次任务开始执行时下一次任务的时间就已经确定了,由于本次任务的“超时”执行,下一次任务的等待时间就会被压缩甚至阻塞,算了画张图就明白了。
SpringBoot实战(二)Spring内置的定时任务_第3张图片

2.1.3.4 initialDelay

initialDelay: 初始化延迟时间,也就是第一次延迟执行的时间。这个参数对 cron 属性无效,只能配合 fixedDelayfixedRate 使用。如 @Scheduled(initialDelay=5000,fixedDelay = 1000) 表示第一次延迟 5000 毫秒执行,下一次任务在上一次任务结束后 1000 毫秒后执行。

2.1.4 cron表达式详解

原文链接:https://cloud.tencent.com/developer/article/1674682

2.1.5 Spring Task 的弊端

Spring 的定时任务默认是单线程执行,多任务情况下,如果使用多线程会影响定时策略

@Component
public class TaskService {
    /**
     * 上一次任务结束后 1 秒,执行下一次任务,任务消耗 5秒
     *
     * @throws InterruptedException the interrupted exception
     */
    @Scheduled(fixedDelay = 1000)
    public void task() throws InterruptedException {
        System.out.println("Thread Name : "
                + Thread.currentThread().getName()
                + "  i am a task : date ->  "
                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        Thread.sleep(5000);
    }

    /**
     * 下轮任务在本轮任务开始2秒后执行. 执行时间可忽略不计
     */
    @Scheduled(fixedRate = 2000)
    public void task2() {
        System.out.println("Thread Name : "
                + Thread.currentThread().getName()
                + "  i am a task2 : date ->  "
                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }

}

结果:

Thread Name : scheduling-1  i am a task : date ->  2022-03-07 15:30:58
Thread Name : scheduling-1  i am a task2 : date ->  2022-03-07 15:30:58
Thread Name : scheduling-1  i am a task2 : date ->  2022-03-07 15:30:58
Thread Name : scheduling-1  i am a task2 : date ->  2022-03-07 15:30:59
Thread Name : scheduling-1  i am a task : date ->  2022-03-07 15:30:59
Thread Name : scheduling-1  i am a task : date ->  2022-03-07 15:31:04

结果分析:

也就是说因为单线程阻塞发生了“连锁反应”,导致了任务执行的错乱。@EnableScheduling 注解引入了ScheduledAnnotationBeanPostProcessorsetScheduler(Object scheduler) 有以下的注释:

如果 TaskScheduler 或者 ScheduledExecutorService 没有定义为该方法的参数,该方法将在 Spring IoC 中寻找唯一的 TaskScheduler 或者 名称为 taskScheduler 的 Bean 作为参数,当然你按照查找 TaskScheduler 的方法找一个ScheduledExecutorService 也可以。要是都找不到那么只能使用本地单线程调度器了。

Spring Task 的调用顺序关系为:任务调度线程 调度 任务执行线程 执行 定时任务 所以我们按照上面定义一个 TaskScheduler 在 Spring Boot 自动配置中提供了 TaskScheduler 的自动配置:

@ConditionalOnClass({ThreadPoolTaskScheduler.class})
@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({TaskSchedulingProperties.class})
@AutoConfigureAfter({TaskExecutionAutoConfiguration.class})
public class TaskSchedulingAutoConfiguration {
    public TaskSchedulingAutoConfiguration() {
    }

    @Bean
    @ConditionalOnBean(
        name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
    )
    @ConditionalOnMissingBean({SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class})
    public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
        return builder.build();
    }

    @Bean
    public static LazyInitializationExcludeFilter scheduledBeanLazyInitializationExcludeFilter() {
        return new ScheduledBeanLazyInitializationExcludeFilter();
    }

    @Bean
    @ConditionalOnMissingBean
    public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
        TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
        builder = builder.poolSize(properties.getPool().getSize());
        Shutdown shutdown = properties.getShutdown();
        builder = builder.awaitTermination(shutdown.isAwaitTermination());
        builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
        builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
        builder = builder.customizers(taskSchedulerCustomizers);
        return builder;
    }
}
# 任务调度线程池

# 任务调度线程池大小 默认 1 建议根据任务加大
spring.task.scheduling.pool.size=1
# 调度线程名称前缀 默认 scheduling-
spring.task.scheduling.thread-name-prefix=scheduling-
# 线程池关闭时等待所有任务完成
spring.task.scheduling.shutdown.await-termination=
# 调度线程关闭前最大等待时间,确保最后一定关闭
spring.task.scheduling.shutdown.await-termination-period=


# 任务执行线程池配置

# 是否允许核心线程超时。这样可以动态增加和缩小线程池
spring.task.execution.pool.allow-core-thread-timeout=true
#  核心线程池大小 默认 8
spring.task.execution.pool.core-size=8
# 线程空闲等待时间 默认 60s
spring.task.execution.pool.keep-alive=60s
# 线程池最大数  根据任务定制
spring.task.execution.pool.max-size=
#  线程池 队列容量大小
spring.task.execution.pool.queue-capacity=
# 线程池关闭时等待所有任务完成
spring.task.execution.shutdown.await-termination=true
# 执行线程关闭前最大等待时间,确保最后一定关闭
spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-

配置完后你就会发现定时任务可以并行异步执行了。

2.2 Timer

Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。

public class TestTimer {

    public static void main(String[] args) {

        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task  run:"+ new Date());
            }

        };

        Timer timer = new Timer();

        //安排指定的任务在指定的时间开始进行重复的固定延迟执行。这里是每3秒执行一次
        timer.schedule(timerTask,10,3000);
    }

}

2.3 ScheduledExecutorService

ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

public class TestScheduledExecutorService {

    public static void main(String[] args) {

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

        // 参数:1、任务体 2、首次执行的延时时间
        //      3、任务执行间隔 4、间隔时间单位
        service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
    }
}

你可能感兴趣的:(SpringBoot,spring)