Java 之定时器

一、Java 定时器

推荐阅读:详解java定时任务


  • Timer:一种定时器工具,用来在一个后台线程计划执行指定任务
  • TimerTask:一个抽象类,其子类代表一个可以被 Timer 计划的任务

1、简介

  • Timer 计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔 1 秒执行任务)

(1) Timer

  • Timer 的四个构造方法都能启动计时器线程,同时保证多个线程共享单个 Timer 对象而无需进行外部同步,所以 Timer 线程安全
  • 由于每个 Timer 对象对应单个后台线程,因此顺序执行计时器任务

    注意:若某个定时器任务执行的时间太长,则其后的所有线程都必须等待

当程序初始化完 Timer 后,定时任务会按照设定的时间执行:

  • schedule方法:
    • schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务
    • schedule(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始,并重复固定的延迟执行
    • schedule(TimerTask task, long delay):安排在指定延迟后执行指定的任务
    • schedule(TimerTask task, long delay, long period):安排指定的任务从指定的延迟开始执行,并重复固定的延迟执行
  • scheduleAtFixedRate方法:同 schedule,但侧重点不同
    • scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定速率执行
    • scheduleAtFixedRate(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行

(2) TimerTask

TimerTask 是一个抽象类,由 Timer 安排为一次执行或重复执行的任务

  • 抽象方法 run():用于执行相应计时器任务要执行的操作,因此每一个任务类都必须继承 TimerTask 并重写 run() 方法
  • 非抽象的方法:
    • boolean cancel():取消此计时器任务
    • long scheduledExecutionTime():返回此任务最近实际执行的安排执行时间

(3) schedule 和 scheduleAtFixedRate 对比

  1. schedule(TimerTask task, Date time)schedule(TimerTask task, long delay):若指定的计划执行时间scheduledExecutionTime <= systemCurrentTime,则 task 会被立即执行

    scheduledExecutionTime 不会因为某一个 task 的过度执行而改变

  2. schedule(TimerTask task, Date firstTime, long period)schedule(TimerTask task, long delay, long period):每次执行 task 的计划时间会随着前一个 task 的实际时间而发生改变,即 scheduledExecutionTime(n+1) = realExecutionTime(n) + periodTime

    若第 n 个 task 的执行时间过长,导致 systemCurrentTime>= scheduledExecutionTime(n+1),但此时第 n+1 个 task 不会立刻执行,而是等待第 n 个 task 执行完之后再执行

schedule 方法侧重间隔时间的稳定,而 scheduleAtFixedRate 方法侧重执行频率的稳定

  • schedule 方法会因为前一个任务的延迟而导致其后面的定时任务延时,而 scheduleAtFixedRate 方法则不会
  • 若第 n 个 task 执行时间过长导致 systemCurrentTime >= scheduledExecutionTime(n+1),则 scheduleAtFixedRate 会立即执行第 n+1 个 task

    scheduleAtFixedRate 计算方法永远保持不变:scheduledExecutionTime(n) = firstExecuteTime + n*periodTime

2、实例

(1) 指定延迟时间执行定时任务

Timer timer = new Timer();
timer.schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	System.out.println("Time's up!!!");
    }
},3 * 1000);

//结果:Time's up!!!

(2) 在指定时间执行定时任务

public static void main(String[] args) throws Exception {
     
	Timer timer = new Timer();
    timer.schedule(new TimerTask() {
     
    	@Override
        public void run() {
     
        	System.out.println("指定时间执行线程任务...");
        }
    }, getTime());
}

public static Date getTime() {
     
	Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 11);
    calendar.set(Calendar.MINUTE, 39);
    calendar.set(Calendar.SECOND, 00);
    return calendar.getTime();
}

//当时间到达(或超过) 11:39:00 时,执行
//结果:指定时间执行线程任务...

(3) 在延迟指定时间后以指定的间隔时间循环执行定时任务

new Timer().schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	Date date = new Date(this.scheduledExecutionTime());
        System.out.println("本次执行该线程的时间为:" + date);
    }
}, 1000, 2000);

//结果:会不断重复执行并重复打印
本次执行该线程的时间为:Mon Aug 31 10:58:29 CST 2020
...

3、缺陷

(1) Timer 缺陷

  • Timer 对调度的支持基于绝对时间,所以对系统时间的改变非常敏感
  • Timer 线程不会捕获异常,若 TimerTask 抛出了未检查异常而导致 Timer 线程终止,则 Timer 不会恢复线程的执行,会错误的认为整个 Timer 线程都会取消,同时尚未执行的 TimerTask 不会再执行,新的任务也不能被调度

    因此,若 TimerTask 抛出未检查的异常,Timer 将会产生无法预料的行为

1. 管理时间缺陷

long start = System.currentTimeMillis();
Timer timer = new Timer();
//timerOne
timer.schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	System.out.println("timerOne invoked,the time: " + (System.currentTimeMillis() - start));
        try {
     
        	Thread.sleep(4000);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
    }
}, 1000);
//timerTwo
timer.schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	System.out.println("timerTwo invoked,the time: " + (System.currentTimeMillis() - start));
    }
}, 3000);

//结果:
timerOne invoked,the time: 1002
timerTwo invoked,the time: 5005	

2. 抛出异常缺陷

Timer timer = new Timer();
//timerOne
timer.schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	throw new RuntimeException();
    }
}, 1000);
//timerTwo
timer.schedule(new TimerTask() {
     
	@Override
    public void run() {
     
    	System.out.println("此处会不会执行?");
    }
}, 1000);

//结果:
Exception in thread "Timer-0" java.lang.RuntimeException
	at org.example.Main$1.run(Main.java:74)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

(2) 用 ScheduledExecutorService 替代 Timer

1. 解决问题一

long start = System.currentTimeMillis();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
//timerOne
scheduledThreadPool.schedule(() -> {
     
	System.out.println("timerOne,the time: " + (System.currentTimeMillis() - start));
    try {
     
    	Thread.sleep(4000);
    } catch (InterruptedException e) {
     
        e.printStackTrace();
    }
}, 1000, TimeUnit.MILLISECONDS);
//timerTwo
scheduledThreadPool.schedule(() -> {
     
	System.out.println("timerTwo,the time: " + (System.currentTimeMillis() - start));
}, 2000, TimeUnit.MILLISECONDS);

//结果:
timerOne,the time: 1052
timerTwo,the time: 2052

2. 解决问题二

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
//timerOne
scheduledThreadPool.schedule(() -> {
     
	throw new RuntimeException();
}, 1000, TimeUnit.MILLISECONDS);
//timerTwo
scheduledThreadPool.scheduleAtFixedRate(() -> {
     
	System.out.println("timerTwo invoked...");
}, 2000, 500, TimeUnit.MILLISECONDS);

//结果:
timerTwo invoked...
...

二、quartz 定时器

推荐阅读:

  • quartz 任务调度
  • quartz 中文文档

1、简介

(1) 基础组件

Quartz 中用到的设计模式:

  • Builder模式
  • Factory模式
  • 组件模式
  • 链式模式

Quartz组成部分

  • 调度器:scheduler
  • 任务:
    • Job:由调度程序执行的组件实现的接口
    • JobDetail:用于定义作业的实例
    • JobBuilder:用于定义/构建 JobDetail 实例,用于定义作业的实例
  • 触发器:Trigger,包括SimpleTrigger和CronTrigger
    • TriggerBuilder:用于定义/构建触发器实例

(2) Maven 依赖

<dependency>  
    <groupId>org.quartz-schedulergroupId>  
    <artifactId>quartzartifactId>  
    <version>2.3.2version>  
dependency>  

2、任务 Job

(1) Job 与 JobDetail

  • Job 是 Quartz 中的一个接口,只有 execute 方法,在这个方法中编写业务逻辑

    每次调度执行 Job 时,调用 execute 方法前会创建一个新的 Job 实例,执行完后,关联的 Job 对象实例会被释放

  • JobDetail 用来绑定 Job,为 Job 实例提供了许多属性,以及 JobDataMap 成员变量属性

    调度器 scheduler 通过 JobDetail 对象来添加 Job 实例
    属性:name、group、jobClass、jobDataMap

public static void main(String[] args) throws Exception {
     
	//创建调度器Schedule
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    //创建JobDetail实例,并与HelloWordlJob类绑定
    JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class)
    								.withIdentity("job1", "jobGroup1")
    								.build();
    //创建触发器Trigger实例(立即执行,每隔1S执行一次)
    Trigger trigger = TriggerBuilder.newTrigger()
    					.withIdentity("trigger1", "triggerGroup1")
                		.startNow()
                		.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
			            .build();
	//开始执行
    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();
}

public class HelloWorldJob implements Job {
     
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
     
        String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
        System.out.println(strTime + ":Hello World!");
    }
}

Job 属性详解:

  • withIdentity:定义标识符,任务名称 name 和任务所属组 group
  • usingJobData/setJobData:传入自定义参数,可以通过 JobExecutionContext 获取
  • withDescription:设置描述
  • ofType:引用Job Class Name
  • storeDurably:若一个 job 非持久,当没有活跃的 trigger 与之关联时,会被自动地从 scheduler 中删除

    即:非持久的 job 生命期由 trigger 的存在与否决定

  • requestRecovery:若一个 job 可恢复,当 scheduler 硬关闭后重启时,该 job 会被重新执行

    此时,该 job 的 JobExecutionContext.isRecovering() 返回 true

(2) JobExecutionContext

  • 当调度器 Scheduler 调用一个 Job 时,会将 JobExecutionContext 传递给 Job 的 execute() 方法
  • Job 能通过 JobExecutionContext 对象访问到 Quartz 运行时的环境以及 Job 本身的详细数据信息
public static void main(String[] args) throws Exception {
     
	//创建调度器Schedule
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    //创建JobDetail实例,并与HelloWordlJob类绑定
    JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class).withIdentity("job1", "jobGroup1").build();
    //创建触发器Trigger实例(立即执行,每隔1S执行一次)
    Trigger trigger = TriggerBuilder.newTrigger()
    					.withIdentity("trigger1", "triggerGroup1")
                		.startNow()
                		.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
			            .build();
	//开始执行
    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();
}

public class HelloWorldJob implements Job {
     
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
     
        String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
        System.out.println(strTime + ":Hello World!");
        System.out.println("JobDetail'name:" + jobExecutionContext.getJobDetail().getKey().getName());
        System.out.println("JobDetail'group:" + jobExecutionContext.getJobDetail().getKey().getGroup());
        System.out.println("JobDetail'class:" + jobExecutionContext.getJobDetail().getClass());
    }
}

//结果:
15-29-50:Hello World!
JobDetail'name:job1
JobDetail'group:jobGroup1
JobDetail'class:class org.quartz.impl.JobDetailImpl

(3) JobDataMap

  • 任务调度时可以通过 JobExecutionContext 获取 JobDataMap,可被序列化

    JobDataMap 实现了JDK的Map接口,可以以 Key-Value 的形式存储数据

usingJobData:可设置 key-value 值,并通过 JobExecutionContext 获取

public static void main(String[] args) throws Exception {
     
	//创建调度器Schedule
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    //创建JobDetail实例,并与HelloWordlJob类绑定
    JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class)
                					.withIdentity("job1", "jobGroup1")
                					.usingJobData("key1", "this is jobDetail")
                					.build();
	//创建触发器Trigger实例(立即执行,每隔1S执行一次)
    Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .usingJobData("key2", "this is trigger")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
                .build();
        //开始执行
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }

@Data
public class HelloWorldJob implements Job {
     
    private String key1;
    private String key2;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
     
        String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
        System.out.println(strTime + ":Hello World!");
        //获取DataMap数据方法一
        System.out.println("JobDetail JobDataMap:" + jobExecutionContext.getJobDetail().getJobDataMap().get("key1"));
        System.out.println("Trigger JobDataMap:" + jobExecutionContext.getTrigger().getJobDataMap().get("key2"));
        //获取DataMap数据方法二
        System.out.println("JobDataMap:" + jobExecutionContext.getMergedJobDataMap().get("key1"));
        System.out.println("JobDataMap:" + jobExecutionContext.getMergedJobDataMap().get("key2"));
        //获取数据方法三
        System.out.println("通过成员变量获取" + key1);
        System.out.println("通过成员变量获取" + key2);
    }
}

//结果:
15-33-26:Hello World!
JobDetail JobDataMap:this is jobDetail
Trigger JobDataMap:this is trigger
JobDataMap:this is jobDetail
JobDataMap:this is trigger
通过成员变量获取this is jobDetail
通过成员变量获取this is trigger

(4) Job 状态与并发

  • @DisallowConcurrentExecution:添加到 job 类上,告诉 Quartz 不要并发地执行同一个 job 类的多个实例
  • @PersistJobDataAfterExecution:添加到 job 类上,告诉 Quartz 成功执行 job 类的 execute 方法后(无异常),更新JobDetail 中 JobDataMap 的数据,使得该 job(即 JobDetail)在下一次执行时,JobDataMap 中是更新后的数据

注意:

  • 上述两个注解针对 job 实例,而不是 job 类
  • 建议同时使用 @PersistJobDataAfterExecution 和 @DisallowConcurrentExecution 注解,
    因为当同一个 job(JobDetail)的两个实例并发执行时,JobDataMap 中的数据很可能不确定

3、触发器 Trigger

(1) Trigger

1. 触发器接口基本介绍

  • 触发器状态 TriggerState
    • NONE
    • NORMAL 正常状态
    • PAUSED 暂停状态
    • COMPLETE 完成
    • ERROR 错误
    • BLOCKED 堵塞
  • 执行完成时状态 CompletedExecutionInstruction
    • NOOP
    • RE_EXECUTE_JOB 重复执行
    • SET_TRIGGER_COMPLETE 触发器执行完成
    • DELETE_TRIGGER 删除触发器
    • SET_ALL_JOB_TRIGGERS_COMPLETE 所有作业和触发器执行完成
    • SET_TRIGGER_ERROR 触发器执行错误
    • SET_ALL_JOB_TRIGGERS_ERROR 设置所有都是错误的
  • TriggerTimeComparator
    • getKey 获取触发器key值
    • getJobKey 获取作业key
    • getDescription 获取描述
    • getCalendarName 获取日历名称
    • getJobDataMap 获取作业数据map
    • getPriority 获取优先级
    • mayFireAgain 是否重复执行
    • getStartTime 开始时间
    • getEndTime 结束时间
    • getNextFireTime 下一次执行时间
    • getPreviousFireTime 上一执行时间
    • getFireTimeAfter(Date afterTime) 获取某个时间后的运行时间
    • getFinalFireTime 获取最后执行时间
    • getMisfireInstruction 获取失败策略
    • getTriggerBuilder 获取触发器建造者
    • getScheduleBuilder 获取调度类建造者
  • 失败策略
    • MISFIRE_INSTRUCTION_SMART_POLICY
    • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
    • DEFAULT_PRIORITY

2. 通用属性

Trigger 是 Quartz 中的触发器,任务执行时会通知调度器 Scheduler 何时触发,几个重要的属性:

  • Jobkey:表示 job 实例的标识
  • StartTime:表示触发器首次被触发的时间(Java.util.Date)
  • EndTime:表示触发器结束触发的时间(Java.util.Date)
public static void main(String[] args) throws Exception {
     
	//创建调度器Schedule
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    //创建JobDetail实例,并与HelloWordlJob类绑定
    JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class)
                					.withIdentity("job1", "jobGroup1")
                					.build();
	//创建触发器Trigger实例(5S后执行,10S后结束)
    Date date1 = new Date();
    date1.setTime(date1.getTime() + 5000);
    //结束时间(10S后)
    Date date2 = new Date();
    date2.setTime(date2.getTime() + 10000);
    Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .startAt(date1)
                .endAt(date2)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
                .build();
	//开始执行
    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();
}

3. 属性详解:

  • withIdentity:定义标识符,根据 name 和 group 创建 trigger 的 key
  • startNowstartAt/endAt:开始时间
  • withSchedule:定义执行频度
  • usingJobData:传入自定义参数,可以通过 JobExecutionContext 获取
  • forJob:引用Job Class
  • withDescription:设置描述
  • withPriority:设置优先级

    注意:Priority 只会在同时触发 Trigger 时比较,10:59 的 Trigger 总比 11:00 的 Trigger 早执行,不管 priority 值是多少

  • modifiedByCalendar:在设置的 calendar 周期内的触发将会被忽略

    例如:可以创建一个 trigger,并设置在每个工作日的上午 9:30 触发,然后添加 Calendar 来去除所有的节假日

    HolidayCalendar cal = new HolidayCalendar();
    cal.addExcludedDate( someDate );
    cal.addExcludedDate( someOtherDate );
    sched.addCalendar("myHolidays", cal, false);
    Trigger t = newTrigger()
    	.withIdentity("myTrigger")
    	.forJob("myJob")
    	.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
     	.modifiedByCalendar("myHolidays") // but not on holidays
     	.build();
    

(2) Tigger 实现类

1. SimpleTrigger 简单触发器

  • SimpleTrigger 可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务
  • SimpleTrigger 是精准指定间隔

属性

  • repeatInterval:重复间隔
  • repeatCount:重复次数(实际执行次数是 repeatCount+1,因为在startTime的时候一定会执行一次)
//创建调度器Schedule
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建JobDetail实例,并与HelloWordlJob类绑定
JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class)
                .withIdentity("job1", "jobGroup1")
                .build();
//创建触发器Trigger实例(5S后执行,一直执行)
Date date1 = new Date();
date1.setTime(date1.getTime() + 5000);
SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .startAt(date1)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(3))
                .build();
//开始执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
源码中的相关属性方法
  • 构造函数私有化:protected SimpleScheduleBuilder()
  • 获取简单调度器:public static SimpleScheduleBuilder simpleSchedule()
  • 创建一个Trigger:public MutableTrigger build()

一直执行

  • 1分钟执行(一直执行):public static SimpleScheduleBuilder repeatMinutelyForever()
  • 每隔几分钟执行(一直执行):public static SimpleScheduleBuilder repeatMinutelyForever(int minutes)
  • 1秒执行(一直执行):public static SimpleScheduleBuilder repeatSecondlyForever()
  • 每隔几秒钟执行(一直执行):public static SimpleScheduleBuilder repeatSecondlyForever(int seconds)
  • 1小时执行(一直执行):public static SimpleScheduleBuilder repeatHourlyForever()
  • 每隔几小时钟执行(一直执行):public static SimpleScheduleBuilder repeatHourlyForever(int hours)

  • 间隔 1分钟,总执行次数为 count:public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count)
  • 间隔时间自定义,执行次数自定义:
    • public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count, int minutes)
    • public static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count)
    • public static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count, int seconds)
    • public static SimpleScheduleBuilder repeatHourlyForTotalCount(int count)
    • public static SimpleScheduleBuilder repeatHourlyForTotalCount(int count, int hours)

几秒钟重复执行:

  • public SimpleScheduleBuilder withIntervalInMilliseconds(long intervalInMillis)
  • public SimpleScheduleBuilder withIntervalInSeconds(int intervalInSeconds)
  • public SimpleScheduleBuilder withIntervalInMinutes(int intervalInMinutes)
  • public SimpleScheduleBuilder withIntervalInHours(int intervalInHours)

  • 重复执行次数:public SimpleScheduleBuilder withRepeatCount(int triggerRepeatCount)

  • public SimpleScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires()
    • 以错过的第一个频率时间立刻开始执行
    • 重做错过的所有频率周期后
    • 当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行
  • public SimpleScheduleBuilder withMisfireHandlingInstructionFireNow()
    • 以当前时间为触发频率立即触发执行
    • 执行至FinalTIme的剩余周期次数
    • 以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
    • 调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
  • public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithExistingCount()
    • 不触发立即执行
    • 等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
    • 以startTime为基准计算周期频率,并得到FinalTime
    • 即使中间出现pause,resume以后保持FinalTime时间不变
  • public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithRemainingCount()
    • 不触发立即执行
    • 等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
    • 以startTime为基准计算周期频率,并得到FinalTime
    • 即使中间出现pause,resume以后保持FinalTime时间不变
  • public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithExistingCount()
    • 以当前时间为触发频率立即触发执行
    • 执行至FinalTIme的剩余周期次数
    • 以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
    • 调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
  • public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithRemainingCount()
    • 以当前时间为触发频率立即触发执行
    • 执行至FinalTIme的剩余周期次数
    • 以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
案例

创建一个在某个时间点执行的 Trigger

  SimpleTrigger trigger = (SimpleTrigger) newTrigger() 
    .withIdentity("trigger1", "group1")
    .startAt(myStartTime) // some Date 
    .forJob("job1", "group1") // identify job with name, group strings
    .build();

创建一个在某个时间点执行的 Trigger,并且随后每 10 秒执行一次,执行 10 次

 trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(myTimeToStartFiring)  // if a start time is not given (if this line were omitted), "now" is implied
    .withSchedule(simpleSchedule()
        .withIntervalInSeconds(10)
        .withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
    .forJob(myJob) // identify job with handle to its JobDetail itself                   
    .build();

创建一个 Trigger,并且在 5 分钟后执行一次

trigger = (SimpleTrigger) newTrigger() 
    .withIdentity("trigger5", "group1")
    .startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
    .forJob(myJobKey) // identify job with its JobKey
    .build();

创建一个 Trigger,并且立即执行一次,以后每 5 分钟执行一次,直到 22:00

  trigger = newTrigger()
    .withIdentity("trigger7", "group1")
    .withSchedule(simpleSchedule()
        .withIntervalInMinutes(5)
        .repeatForever())
    .endAt(dateOf(22, 0, 0))
    .build();

创建一个 Trigger,并且在下一小时开始的时候执行一次,以后每两小时执行一次,永久循环

trigger = newTrigger()
    .withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
    .startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
    .withSchedule(simpleSchedule()
        .withIntervalInHours(2)
        .repeatForever())
    // note that in this example, 'forJob(..)' is not called 
    //  - which is valid if the trigger is passed to the scheduler along with the job  
    .build();
     
    scheduler.scheduleJob(trigger, job);
SimpleTrigger 的错过触发机制
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

其中 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY (智能策略)是所有 Trigger 的默认值


当创建 SimpleTrigger 的时候,可以通过 SimpleSchedulerBuilder 指定错过触发机制:

 trigger = newTrigger()
    .withIdentity("trigger7", "group1")
    .withSchedule(simpleSchedule()
        .withIntervalInMinutes(5)
        .repeatForever()
        .withMisfireHandlingInstructionNextWithExistingCount())
    .build();

2. CronTrigger cron表达式触发器

  • Cron 在线生成器

CronTrigger 基于日历的作业调度,基于 Cron 表达式:[秒] [分] [小时] [日] [月] [周] [年]

//创建调度器Schedule
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建JobDetail实例,并与HelloWordlJob类绑定
JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class)
                .withIdentity("job1", "jobGroup1")
                .build();
//创建触发器CronTrigger实例(每周一到周五10:30执行任务)
CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 *"))
                .build();
//开始执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();

Java 之定时器_第1张图片


源码中的相关属性方法
  • 构造函数私有化:protected CronScheduleBuilder(CronExpression cronExpression)、public MutableTrigger build()
  • 根据cron表达式建造:public static CronScheduleBuilder cronSchedule(String cronExpression)
  • 利用CronExpression建造:public static CronScheduleBuilder cronSchedule(CronExpression cronExpression)
  • 核查表达式是否正确:public static CronScheduleBuilder cronScheduleNonvalidatedExpression(String cronExpression) throws ParseException

    表达式异常:cronScheduleNoParseException

  • 每天在指定的时间执行(自动创建 cron 表达式):public static CronScheduleBuilder dailyAtHourAndMinute(int hour, int minute)
  • 在指定的天数、时间执行(自动创建 cron 表达式):public static CronScheduleBuilder atHourAndMinuteOnGivenDaysOfWeek(int hour, int minute, Integer... daysOfWeek)
  • 每周的某天,在指定的时间执行:public static CronScheduleBuilder weeklyOnDayAndHourAndMinute(int dayOfWeek, int hour, int minute)
  • 每月的某天,在指定的时间执行:public static CronScheduleBuilder monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute)
  • 设置时区:public CronScheduleBuilder inTimeZone(TimeZone timezone)
  • 设置处理办法:
    • public CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires()
    • public CronScheduleBuilder withMisfireHandlingInstructionDoNothing()
    • public CronScheduleBuilder withMisfireHandlingInstructionFireAndProceed()
案例

创建一个 Trigger,并且在每天的上午 8 点到下午 5 点,每隔 1 分钟执行一次

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
    .forJob("myJob", "group1")
    .build();

创建一个 Trigger,并且在每天的上午 10:42 执行一次

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(dailyAtHourAndMinute(10, 42))
    .forJob(myJobKey)
    .build();
或
trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 42 10 * * ?"))
    .forJob(myJobKey)
    .build();

创建一个 Trigger,并在每周三上午 10:42 执行,使用设置的时区而非系统默认时区:

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))
    .forJob(myJobKey)
    .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
    .build();
或
trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 42 10 ? * WED"))
    .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
    .forJob(myJobKey)
    .build();

CronTrigger 错过触发机制
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW

默认使用 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY (智能策略)

  trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 0/2 8-17 * * ?")
    .withMisfireHandlingInstructionFireAndProceed())
    .forJob("myJob", "group1")
    .build();

3. CalendarIntervalTrigger 日历触发器

  • 相同点:类似 SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务

  • 不同点:

    • SimpleTrigger 指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值)
    • CalendarIntervalTrigger 支持的间隔单位有秒,分钟,小时,天,月,年,星期
  • 较于 SimpleTrigger 的优势:

    1. 更方便,比如:每隔1小时执行,不用自己去计算1小时等于多少毫秒
    2. 支持不固定长度的间隔,比如:间隔为月和年

      劣势是精度只能到秒

  • 适合的任务:类似于:9:00 开始执行,并且以后每周 9:00 执行一次


属性

  • interval:执行间隔
  • intervalUnit:执行间隔的单位(秒,分钟,小时,天,月,年,星期)

案例:

// 每两秒执行
CalendarIntervalTrigger trigger = TriggerBuilder.newTrigger()
			.withIdentity("trigger1", "group1")
			.withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(2, DateBuilder.IntervalUnit.SECOND))
			.build();

方法使用同 SimpleTrigger

CalendarIntervalScheduleBuilder
calendarIntervalSchedule
build

// 和DailyTimeIntervalScheduleBuilder差不多
public CalendarIntervalScheduleBuilder withInterval(int timeInterval, IntervalUnit unit)
withIntervalInSeconds
withIntervalInMinutes
withIntervalInHours
withIntervalInDays
withIntervalInWeeks
withIntervalInMonths
withIntervalInYears

withMisfireHandlingInstructionIgnoreMisfires
withMisfireHandlingInstructionDoNothing
withMisfireHandlingInstructionFireAndProceed
inTimeZone

preserveHourOfDayAcrossDaylightSavings
skipDayIfHourDoesNotExist
validateInterval
interval
intervalUnit
misfireInstruction
timeZone
preserveHourOfDayAcrossDaylightSavings
skipDayIfHourDoesNotExist

4. DailyTimeIntervalTrigger 日期触发器

  • 指定每天的某个时间段内,以一定的时间间隔执行任务,并且可以支持指定星期
  • 适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行

  • 属性:
    • startTimeOfDay:每天开始时间
    • endTimeOfDay:每天结束时间
    • daysOfWeek:需要执行的星期
    • interval:执行间隔
    • intervalUnit:执行间隔的单位(秒,分钟,小时,天,月,年,星期)
    • repeatCount:重复次数

案例:

DailyTimeIntervalTrigger trigger = dailyTimeIntervalSchedule()
    .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) // 第天9:00开始
    .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) // 16:00 结束 
    .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) // 周一至周五执行
    .withIntervalInHours(1) // 每间隔1小时执行一次
    .withRepeatCount(100) // 最多重复100次(实际执行100+1次)
    .build();
    
DailyTimeIntervalTrigger trigger = dailyTimeIntervalSchedule()
    .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) // 第天9:00开始
    .endingDailyAfterCount(10) // 每天执行10次,这个方法实际上根据 startTimeOfDay+interval*count 算出 endTimeOfDay
    .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) // 周一至周五执行
    .withIntervalInHours(1) // 每间隔1小时执行一次
    .build();
    
// 每两秒执行
DailyTimeIntervalTrigger trigger = TriggerBuilder.newTrigger()
		.withIdentity("trigger1", "group1")
		.withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().withInterval(2, DateBuilder.IntervalUnit.SECOND))
		.build();

DailyTimeIntervalScheduleBuilder()

dailyTimeIntervalSchedule()

build()

withInterval(int timeInterval, IntervalUnit unit) //执行时间间隔触发执行,unit时间单位 
withIntervalInSeconds(int intervalInSeconds)    //秒 
withIntervalInMinutes(int intervalInMinutes)    //分钟 
withIntervalInHours(int intervalInHours)    //小时 

// 周几执行 
onDaysOfTheWeek(Set<Integer> onDaysOfWeek)
onDaysOfTheWeek(Integer... onDaysOfWeek)

onMondayThroughFriday()
onSaturdayAndSunday()
onEveryDay()

startingDailyAt(TimeOfDay timeOfDay)    // 开始触发时间 
endingDailyAt(TimeOfDay timeOfDay)    //结束时间 
endingDailyAfterCount(int count)    

withMisfireHandlingInstructionIgnoreMisfires()
withMisfireHandlingInstructionDoNothing()
withMisfireHandlingInstructionFireAndProceed()

//重复次数 
withRepeatCount() 
validateInterval()

// 常量等 
interval 
intervalUnit 
daysOfWeek 
startTimeOfDay 
endTimeOfDay 
repeatCount 
misfireInstruction 
ALL_DAYS_OF_THE_WEEK 
MONDAY_THROUGH_FRIDAY 
SATURDAY_AND_SUNDAY

5. NthIncludedDayTrigger

  • 用于在每一间隔类型的第几天执行 Job
  • 适合场景:在每个月的 15 号执行 Job

    Quartz 的 Caldendar 也可与 Trigger 关联以此把周末与节假日考虑进来,并在必要时跳开这些日期


案例:

NthIncludedDayTrigger trigger = new NthIncludedDayTrigger("MyTrigger", Scheduler.DEFAULT_GROUP);
trigger.setN(15);
trigger.setIntervalType(NthIncludedDayTrigger.INTERVAL_TYPE_MONTHLY);

4、Quartz 之 Calendar 排除指定节假日

注意:Quartz 的 Calendar 对象与 Java API 的 java.util.Calendar,它们是应用于不同目的不一样的组件

  • Java 的 Calendar 对象是通用的日期和时间工具;许多过去由 Java 的 Date 类提供的功能现在加到 Calendar 类中
  • Quartz 的 Calendar 专门用于屏闭一个时间区间,使 Trigger 在这个区间中不被触发

Calendar 接口方法参数的类型是 Long,这说明 Quartz Calendar 能够排除的时间细致毫秒级


  • AnnualCalendar:指定每年的哪一天,精度是【天】
  • CronCalendar:指定 Cron 表达式,精度取决于 Cron 表达式
  • DailyCalendar:指定每天的时间段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]],精度是【毫秒】
  • HolidayCalendar:指定特定的日期,精度到【天】
  • MonthlyCalendar:指定每月的几号,可选值为1-31,精度是【天】
  • WeeklyCalendar:指定每星期的星期几,可选值比如为 java.util.Calendar.SUNDAY,精度是【天】

(1) AnnualCalendar

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("-------  初始化 ----------");
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

// 声明一个节假日 holidayCalendar,标明要排除的日期
// 法定节日是以每年为周期的,所以使用AnnualCalendar而不是HolidayCalendar
AnnualCalendar holidays = new AnnualCalendar();

Calendar fourthOfJuly = new GregorianCalendar(2017, 6, 4);
holidays.setDayExcluded(fourthOfJuly, true);
System.out.println("第一个节假日:" + sdf.format(fourthOfJuly.getTime()));

Calendar halloween = new GregorianCalendar(2017, 9, 31);
holidays.setDayExcluded(halloween, true);
System.out.println("第二节假日:" + sdf.format(halloween.getTime()));

Calendar christmas = new GregorianCalendar(2017, 11, 25);
holidays.setDayExcluded(christmas, true);
System.out.println("第三个节假日:" + sdf.format(christmas.getTime()));

sched.addCalendar("holidays", holidays, false, false); // 节假日加入schedule调度器

// 开始在万圣节前夜上午10点,开始任务
Date runDate = dateOf(0, 0, 10, 31, 10);
System.out.println("任务开始时间:" + sdf.format(runDate));

JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
SimpleTrigger trigger = newTrigger()
				.withIdentity("trigger1", "group1")
				.startAt(runDate)
				.withSchedule(simpleSchedule().withIntervalInHours(1).repeatForever())
				.modifiedByCalendar("holidays")
				.build();
Date firstRunTime = sched.scheduleJob(job, trigger);

// 注意:万圣节(10月31日)是假期,所以直到第二天才会运行! (11月1日)
System.out.println(job.getKey() + " 将运行于:" + firstRunTime + " 并重复:"
				+ trigger.getRepeatCount() + " 次, 间隔 "
				+ trigger.getRepeatInterval() / 1000 + " 秒");

System.out.println("------- 开始 Scheduler ----------------");
sched.start();

System.out.println("------- 等待 30 秒... --------------");
try {
     
	Thread.sleep(30L * 1000L);
} catch (Exception e) {
     
}

sched.shutdown(true);
System.out.println("------- 关闭调度器 -----------------");

SchedulerMetaData metaData = sched.getMetaData();
System.out.println("执行了: " + metaData.getNumberOfJobsExecuted() + " 个jobs.");

(2) CronCalendar

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("-------  初始化 ----------");
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
// 允许执行的时间, 星期参数:"7" = "SAT",2 = MON
// 【秒】 【分钟】 【小时】 【月中天】 【月】 【周中天(1-7)】 [【年(可省略)】]
String excludeExpression;

// 这里设置禁用时间段为,每0-20之间,40-59之间不执行
excludeExpression = "0-20,40-59 * * * * ?";
CronCalendar cronCalendar = new CronCalendar(excludeExpression);
// 标明要排除的日期 每天的17点10分
sched.addCalendar("cronCalendar", cronCalendar, false, false); // 节假日加入schedule调度器

Date runDate = new Date();
System.out.println("任务开始时间:" + sdf.format(runDate));
// 任务每10秒执行一次
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
SimpleTrigger trigger = newTrigger()
				.withIdentity("trigger1", "group1")
				.startAt(runDate)
				.withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever())
				.modifiedByCalendar("cronCalendar")
				.build();
// 触发器加入调度器
Date firstRunTime = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " 将运行于:" + sdf.format(firstRunTime)
				+ " 并重复:" + trigger.getRepeatCount() + " 次, 间隔 "
				+ trigger.getRepeatInterval() / 1000 + " 秒");

System.out.println("------- 开始 Scheduler ----------------");
sched.start();

try {
     
	System.out.println("------- 等待 120 秒(2分钟)... --------------");
	Thread.sleep(120L * 1000L);
} catch (Exception e) {
     
}

sched.shutdown(true);
System.out.println("------- 关闭调度器 -----------------");

SchedulerMetaData metaData = sched.getMetaData();
System.out.println("~~~~~~~~~~  执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs.");

(3) DailyCalendar

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("-------  初始化 ----------");
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

DailyCalendar dailyCalendar = new DailyCalendar("12:17:30", "12:18:20");
dailyCalendar.setInvertTimeRange(true); // 时间反转,为true表示只有这次时间段才会被执行,为false表示排除这时间段

// 标明要排除的日期 每天的17点10分
sched.addCalendar("dailyCalendar", dailyCalendar, false, false); // 节假日加入schedule调度器

Date runDate = new Date();
System.out.println("任务开始时间:" + sdf.format(runDate));
// 任务每10秒执行一次
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
SimpleTrigger trigger = newTrigger()
				.withIdentity("trigger1", "group1")
				.startAt(runDate)
				.withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever())
				.modifiedByCalendar("dailyCalendar")
				.build();
Date firstRunTime = sched.scheduleJob(job, trigger);

System.out.println(job.getKey() + " 将运行于:" + sdf.format(firstRunTime)
				+ " 并重复:" + trigger.getRepeatCount() + " 次, 间隔 "
				+ trigger.getRepeatInterval() / 1000 + " 秒");

System.out.println("------- 开始 Scheduler ----------------");
sched.start();

System.out.println("------- 等待 360 秒(3分钟)... --------------");
try {
     
	Thread.sleep(360L * 1000L);
} catch (Exception e) {
     
}

sched.shutdown(true);
System.out.println("------- 关闭调度器 -----------------");

SchedulerMetaData metaData = sched.getMetaData();
System.out.println("~~~~~~~~~~  执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs.");

(4) HolidayCalendar

HolidayCalendar holidayCalendar = new HolidayCalendar();
Calendar calendar = new GregorianCalendar(2017, 10, 1); // 2017年11月1日
holidayCalendar.addExcludedDate(calendar.getTime());

calendar = new GregorianCalendar(2018, 10, 2); // 2018年11月2日
holidayCalendar.addExcludedDate(calendar.getTime());

holidayCalendar.getExcludedDates().forEach(date -> {
     
    System.out.println("假期日:"+ sdf.format(date));
});

sched.addCalendar("holidays", holidayCalendar, false, false); // 节假日加入schedule调度器

(5) MonthlyCalendar

// 设置2,3,4月不触发任务
MonthlyCalendar monthlyCalendar = new MonthlyCalendar();
monthlyCalendar.setDayExcluded(2, true);
monthlyCalendar.setDayExcluded(3, true);
monthlyCalendar.setDayExcluded(4, true);

sched.addCalendar("monthlys", monthlyCalendar, false, false); // 节假日加入schedule调度器

(6) WeeklyCalendar

WeeklyCalendar weeklyCalendar = new WeeklyCalendar();
weeklyCalendar.setDayExcluded(Calendar.THURSDAY, true);
sched.addCalendar("weeklys", weeklyCalendar, false, false); // 节假日加入schedule调度器
DailyCalendar dailyCalendar = new DailyCalendar("8:00:00", "17:00:00");
dailyCalendar.setInvertTimeRange(false);
        
WeeklyCalendar weeklyCalendar = new WeeklyCalendar(dailyCalendar);
sched.addCalendar("weeklyCalendar", weeklyCalendar, false, false);

5、Quartz 任务调度信息持久化到 DB

  • Quartz 任务调度信息持久化到 DB

三、Spring 定时器

  • 推荐阅读:玩转SpringBoot之定时任务详解

1、基于注解 @Scheduled

(1) xml 配置

配置文件中增加 task 命名空间:

xmlns:task="http://www.springframework.org/schema/task" 

http://www.springframework.org/schema/task   
http://www.springframework.org/schema/task/spring-task.xsd

配置 Spring task 扫描:

 
 <task:annotation-driven/>

(2) 案例

1. Spring配置文件


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/task
                           http://www.springframework.org/schema/task/spring-task.xsd">
    
    <context:component-scan base-package="com.xgj.quartz.quartzWithSpring.anno"/>
    
    <task:annotation-driven/>
beans>

2. 带有注解的 Job 类

@Component
public class MyAnnoJob {
     
	@Scheduled(cron = "*/5 * * * * ?")
	// 每隔5秒执行一次
	public void test() throws Exception {
     
		System.out.println("Spring集成Quartz 使用 Annotation的方式......");
	}
}

3. 测试类

public class SpringQuartzAnnoTest {
     
	public static void main(String[] args) {
     
		// 启动Spring 容器
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
				"classpath:com/xgj/quartz/quartzWithSpring/anno/spring-quartz-anno.xml");
		System.out.println("initContext successfully");
	}
}

(3) @Scheduled 配置详解

1. @Scheduled 源码

@Target({
     ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
     
    String cron() default "";
    String zone() default "";
    long fixedDelay() default -1L;
    String fixedDelayString() default "";
    long fixedRate() default -1L;
    String fixedRateString() default "";
    long initialDelay() default -1L;
    String initialDelayString() default "";
}

2. 配置属性说明

属性类型 属性 属性说明
String cron cron的表达式
String zone cron表达式将被解析的时区
long fixedDelay 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法
String fixedDelayString 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法
long fixedRate 在调用之间以固定的时间段执行带注释的方法
String fixedRateString 在调用之间以固定的时间段执行带注释的方法
long initialDelay 在首次执行 fixedRate 或 fixedDelay 任务之前要延迟的毫秒数
String initialDelayString 在首次执行 fixedRate 或 fixedDelay 任务之前要延迟的毫秒数

2、基于接口 SchedulingConfigurer

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {
     
    @Mapper
    public interface CronMapper {
     
        @Select("select cron from cron limit 1")
        public String getCron();
    }

    @Autowired      //注入mapper
    @SuppressWarnings("all")
    CronMapper cronMapper;

    // 执行定时任务
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
     
        taskRegistrar.addTriggerTask(
                //1.添加任务内容(Runnable)
                () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
                //2.设置执行周期(Trigger)
                triggerContext -> {
     
                    //2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    //2.2 合法性校验.
                    if (StringUtils.isEmpty(cron)) {
     
                        // Omitted Code ..
                    }
                    //2.3 返回执行周期(Date)
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }
}

3、多线程定时任务

//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class MultithreadScheduleTask {
     
	@Async
    @Scheduled(fixedDelay = 1000)  //间隔1秒
    public void first() throws InterruptedException {
     
    	System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
        System.out.println();
        Thread.sleep(1000 * 10);
	}

	@Async
    @Scheduled(fixedDelay = 2000)
    public void second() {
     
    	System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
        System.out.println();
	}
}

你可能感兴趣的:(工作学习总结)