【任务调度】定时任务,SpringTask,Quartz

在 JDK 中,内置了两个类,可以实现定时任务的功能:

  • java.util.Timer :可以通过创建 java.util.TimerTask 调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在 坑坑 ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
  • java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样,ScheduledExecutorService 就解决了 Timer 串行的问题。

但是它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要结合Calendar自行计算,才能实现复杂时间的调度。
它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要多考虑,多个进程下,同一个任务在相同时刻,不能重复执行
项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装
所以我们可以用调度任务中间件

在 Spring 体系中,内置了两种定时任务的解决方案:

  • 第一种,Spring Framework 的 Spring Task 模块,提供了轻量级的定时任务的实现。
  • 第二种,Spring Boot 2.0 版本,整合了 Quartz 作业调度框架,提供了功能强大的定时任务的实现。
    注:Spring Framework 已经内置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自动化配置,而 2.X 版本提供了支持。

Spring Task

  • 创建配置类,启动定时任务
// ScheduleConfiguration.java

@Configuration
@EnableScheduling
public class ScheduleConfiguration {
}

在类上,通过添加 @EnableScheduling 注解,启动 Spring Task 的定时任务调度的功能。

  • 创建任务类
@Component
public class TaskDemo {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private final AtomicInteger counts = new AtomicInteger();

    @Scheduled(fixedRate = 2000)
    public void execute() {
        logger.info("[execute][定时第 ({}) 次执行]", counts.incrementAndGet());
    }
}

其中的 #execute() 方法用于实现任务要执行的内容,这里是打印一下日志。
同时,在该方法上,添加 @Scheduled 注解,设置每 2 秒执行该方法。

@Configuration
@EnableScheduling//开启定时任务
@EnableAsync//开启多线程
pubic class config{
    @Scheduled(cron="0 0 23 * * ?")//每天23点执行
    @Async//异步
    public void method(){}
}
  • @Scheduled常用属性

    @Scheduled 注解用于设置定时任务的执行计划。

    常用属性如下:

    • cron 属性:Spring Cron 表达式。例如说,“0 0 12 * * ?” 表示每天中午执行一次,“11 11 11 11 11 ?” 表示 11 月 11 号 11 点 11 分 11 秒执行一次。注意,以调用完成时刻为开始计时时间。
    • fixedDelay 属性:固定执行间隔,单位:毫秒。注意,以调用完成时刻为开始计时时间。
    • 控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次。cron 和此方式类似。
    • fixedRate 属性:固定执行间隔,单位:毫秒。注意,以调用开始时刻为开始计时时间。
    • 按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行。

    注意三者区别。

    不常用属性如下:

    • initialDelay 属性:初始化的定时任务执行延迟,单位:毫秒。
    • zone 属性:解析 Spring Cron 表达式的所属的时区。默认情况下,使用服务器的本地时区。
    • initialDelayString 属性:initialDelay 的字符串形式。
    • fixedDelayString 属性:fixedDelay 的字符串形式。
    • fixedRateString 属性:fixedRate 的字符串形式。
  • Spring Task配置

    在 application.yml 中,添加 Spring Task 定时任务的配置,如下:

spring:
  task:
    # Spring Task 调度任务的配置,对应 TaskSchedulingProperties 配置类
    scheduling:
      thread-name-prefix: sh-demo- # 线程池的线程名的前缀。默认为 scheduling- ,建议根据自己应用来设置
      pool:
        size: 10 # 线程池大小。默认为 1 ,根据自己应用来设置
      shutdown:
        await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
        await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
  • 在 spring.task.scheduling 配置项中,Spring Task 调度任务的配置对应 TaskSchedulingProperties 配置类。
  • Spring Boot TaskSchedulingAutoConfiguration 自动化配置类,实现 Spring Task 的自动配置,默认创建 ThreadPoolTaskScheduler 基于线程池的任务调度器。
  • 本质上,ThreadPoolTaskScheduler 是基于 ScheduledExecutorService 的封装,增强在调度时间上的功能。
  • 通过配置 await-termination = true ,实现应用关闭时,等待定时任务执行完成。这样,应用在关闭的时,Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后,再开始 Spring Bean 的销毁。
  • 同时,又考虑到我们不可能无限等待定时任务全部执行结束,因此可以配置 await-termination-period = 60 ,等待任务完成的最大时长,单位为秒。具体设置多少的等待时长,可以根据自己应用的需要。

注意,spring.task.scheduling.shutdown 配置项,是为了实现 Spring Task 定时任务的优雅关闭。
我们想象一下,如果定时任务在执行的过程中,如果应用开始关闭,把定时任务需要使用到的 Spring Bean 进行销毁
例如说数据库连接池,那么此时定时任务还在执行中,一旦需要访问数据库,可能会导致报错。

Quartz

在这里插入图片描述
在 Quartz 体系结构中,有三个组件非常重要,它们是 Job,Trigger 和 Scheduler:

  • Job: 任务或作业
    • Job 是一个接口,只定义一个方法 execute (JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job (任务)。
    • JobExecutionContext 类提供了调度应用的一些信息。
    • Job 运行时的信息保存在 JobDataMap 实例中。
      • Job 有一个 StatefulJob 子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让 Quartz 知道任务的类型,以便采用不同的执行方案。
      • 无状态任务在执行时拥有自己的 JobDataMap 拷贝,对 JobDataMap 的更改不会影响下次的执行。
      • 有状态任务则共享同一个 JobDataMap 实例,每次任务执行对 JobDataMap 所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
      • 正因为这个原因,无状态的 Job 可以并发执行,而有状态的 StatefulJob 不能并发执行,这意味着如果前次的 StatefulJob 还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。
      • 有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的 Job。
    • 如果 Quartz 使用了数据库持久化任务调度信息,无状态的 JobDataMap 仅会在 Scheduler 注册任务时保持一次,而有状态任务对应的 JobDataMap 在每次执行任务后都会进行保存。
  • JobDetail: Job 的实现类
    • JobDetail 描述了 Job 的实现类及其它相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance () 的反射机制实例化 Job。
    • Quartz 每次调度 Job 时, 都重新创建一个 Job 实例, 所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类。
    • 可通过 JobBuilder.newJob() 构建。
  • Trigger:触发器
    • 描述触发 Job 执行的时间触发规则。主要使用 SimpleTrigger 和 CronTrigger 这两个子类。
    • 当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择。
    • CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 15:00~16:00 执行调度等;
    • Trigger 自身也可以拥有一个 JobDataMap,其关联的 Job 可以通过 JobExecutionContext#getTrigger ().getJobDataMap () 获取 Trigger 中的 JobDataMap。
    • 不管是有状态还是无状态的任务,在任务执行期间对 Trigger 的 JobDataMap 所做的更改都不会进行持久,也即不会对下次的执行产生影响。
    • Quartz 拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。
    • 可通过 TriggerBuilder.newTrigger() 构建。
  • Scheduler :调度器
    • 代表一个 Quartz 的独立运行容器, Trigger 和 JobDetail 可以注册到 Scheduler 中, 两者在 Scheduler 中拥有各自的组及名称, 组及名称是 Scheduler 查找定位容器中某一对象的依据, Trigger 的组及名称必须唯一, JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。
    • Scheduler 定义了多个接口方法, 允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
    • Scheduler 可以将 Trigger 绑定到某一 JobDetail 中, 这样当 Trigger 触发时, 对应的 Job 就被执行。
    • 一个 Job 可以对应多个 Trigger, 但一个 Trigger 只能对应一个 Job。
    • 可以通过 SchedulerFactory 创建一个 Scheduler 实例。
    • Scheduler 拥有一个 SchedulerContext,它类似于 ServletContext,保存着 Scheduler 上下文信息,Job 和 Trigger 都可以访问 SchedulerContext 内的信息。
    • SchedulerContext 内部通过一个 Map,以键值对的方式维护这些上下文数据,SchedulerContext 为保存和获取数据提供了多个 put () 和 getXxx () 的方法。
    • 可以通过 Scheduler# getContext () 获取对应的 SchedulerContext 实例;

执行任务类继承Job,实现execute方法

  • 创建调度器Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
  • 创建JobDetail实例,并于Job类绑定
 JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                .withIdentity("job1","group1")//任务名
                .build();
  • 构建Trigger实例,并设定隔多少时间执行一次
Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("tigger1","tiggerGroup1")
                .startNow()//立即生效
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1)//每隔1s执行一次
                        .repeatForever())//一直执行
                .build();
  • 执行
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();

Demo

引入依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

创建Job

public class HelloQuartz implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Hello Quartz!");
    }
}

调用Job

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloQuartzApplication {
    public static void main(String[] args) throws SchedulerException, InterruptedException {

        // 1. 通过 SchedulerFactory 获取一个调度器
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();

        // 2. 创建jobDetail实例,绑定Job实现类
        JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("HelloJob", "HelloJobGroup").build();

        // 3. 创建触发器
        //        // SimpleTrggier,定义调度触发规则
        //        SimpleTrigger trigger1 = TriggerBuilder
        //                .newTrigger().withIdentity("HelloSimpleTrigger", "HelloSimpleTriggerGroup")
        //                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3).withRepeatCount(6))
        //                .startNow().build();
        //        // 把作业和触发器注册到任务调度中
        //        scheduler.scheduleJob(job, trigger1);

        //  CronTrigger,corn表达式:每五秒执行一次
        Trigger trigger2 =TriggerBuilder.newTrigger().withIdentity("HelloCronTrigger", "HelloCronTriggerGroup")
            .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
            .startNow().build();
        scheduler.scheduleJob(job, trigger2);

        // 4. 启动调度
        scheduler.start();

        Thread.sleep(18000);

        // 5. 停止调度
        scheduler.shutdown();

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值