推荐阅读:详解java定时任务
Timer
:一种定时器工具,用来在一个后台线程计划执行指定任务TimerTask
:一个抽象类,其子类代表一个可以被 Timer 计划的任务注意:若某个定时器任务执行的时间太长,则其后的所有线程都必须等待
当程序初始化完 Timer 后,定时任务会按照设定的时间执行:
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(TimerTask task, Date firstTime, long period)
:安排指定的任务在指定的时间开始进行重复的固定速率执行scheduleAtFixedRate(TimerTask task, long delay, long period)
:安排指定的任务在指定的延迟后开始进行重复的固定速率执行TimerTask 是一个抽象类,由 Timer 安排为一次执行或重复执行的任务
run()
:用于执行相应计时器任务要执行的操作,因此每一个任务类都必须继承 TimerTask 并重写 run() 方法boolean cancel()
:取消此计时器任务long scheduledExecutionTime()
:返回此任务最近实际执行的安排执行时间schedule(TimerTask task, Date time)
、schedule(TimerTask task, long delay)
:若指定的计划执行时间scheduledExecutionTime <= systemCurrentTime,则 task 会被立即执行
scheduledExecutionTime 不会因为某一个 task 的过度执行而改变
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 方法侧重执行频率的稳定:
scheduleAtFixedRate 计算方法永远保持不变:scheduledExecutionTime(n) = firstExecuteTime + n*periodTime
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Time's up!!!");
}
},3 * 1000);
//结果:Time's up!!!
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 时,执行
//结果:指定时间执行线程任务...
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
...
因此,若 TimerTask 抛出未检查的异常,Timer 将会产生无法预料的行为
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
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)
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
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组成部分
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>2.3.2version>
dependency>
每次调度执行 Job 时,调用 execute 方法前会创建一个新的 Job 实例,执行完后,关联的 Job 对象实例会被释放
调度器 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 和任务所属组 groupusingJobData
/setJobData
:传入自定义参数,可以通过 JobExecutionContext 获取withDescription
:设置描述ofType
:引用Job Class NamestoreDurably
:若一个 job 非持久,当没有活跃的 trigger 与之关联时,会被自动地从 scheduler 中删除
即:非持久的 job 生命期由 trigger 的存在与否决定
requestRecovery
:若一个 job 可恢复,当 scheduler 硬关闭后重启时,该 job 会被重新执行
此时,该 job 的 JobExecutionContext.isRecovering() 返回 true
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
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
@DisallowConcurrentExecution
:添加到 job 类上,告诉 Quartz 不要并发地执行同一个 job 类的多个实例@PersistJobDataAfterExecution
:添加到 job 类上,告诉 Quartz 成功执行 job 类的 execute 方法后(无异常),更新JobDetail 中 JobDataMap 的数据,使得该 job(即 JobDetail)在下一次执行时,JobDataMap 中是更新后的数据注意:
- 上述两个注解针对 job 实例,而不是 job 类
- 建议同时使用 @PersistJobDataAfterExecution 和 @DisallowConcurrentExecution 注解,
因为当同一个 job(JobDetail)的两个实例并发执行时,JobDataMap 中的数据很可能不确定
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
获取作业keygetDescription
获取描述getCalendarName
获取日历名称getJobDataMap
获取作业数据mapgetPriority
获取优先级mayFireAgain
是否重复执行getStartTime
开始时间getEndTime
结束时间getNextFireTime
下一次执行时间getPreviousFireTime
上一执行时间getFireTimeAfter(Date afterTime)
获取某个时间后的运行时间getFinalFireTime
获取最后执行时间getMisfireInstruction
获取失败策略getTriggerBuilder
获取触发器建造者getScheduleBuilder
获取调度类建造者MISFIRE_INSTRUCTION_SMART_POLICY
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
DEFAULT_PRIORITY
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();
}
withIdentity
:定义标识符,根据 name 和 group 创建 trigger 的 keystartNow
与 startAt
/endAt
:开始时间withSchedule
:定义执行频度usingJobData
:传入自定义参数,可以通过 JobExecutionContext 获取forJob
:引用Job ClasswithDescription
:设置描述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();
属性:
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()
public MutableTrigger build()
一直执行:
public static SimpleScheduleBuilder repeatMinutelyForever()
public static SimpleScheduleBuilder repeatMinutelyForever(int minutes)
public static SimpleScheduleBuilder repeatSecondlyForever()
public static SimpleScheduleBuilder repeatSecondlyForever(int seconds)
public static SimpleScheduleBuilder repeatHourlyForever()
public static SimpleScheduleBuilder repeatHourlyForever(int hours)
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()
:
public SimpleScheduleBuilder withMisfireHandlingInstructionFireNow()
:
public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithExistingCount()
:
public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithRemainingCount()
:
public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithExistingCount()
:
public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithRemainingCount()
:
创建一个在某个时间点执行的 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);
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();
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();
protected CronScheduleBuilder(CronExpression cronExpression)、public MutableTrigger build()
public static CronScheduleBuilder cronSchedule(String cronExpression)
public static CronScheduleBuilder cronSchedule(CronExpression cronExpression)
public static CronScheduleBuilder cronScheduleNonvalidatedExpression(String cronExpression) throws ParseException
表达式异常:
cronScheduleNoParseException
public static CronScheduleBuilder dailyAtHourAndMinute(int hour, int minute)
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();
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();
相同点:类似 SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务
不同点:
较于 SimpleTrigger 的优势:
劣势是精度只能到秒
适合的任务:类似于: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
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
Quartz 的 Caldendar 也可与 Trigger 关联以此把周末与节假日考虑进来,并在必要时跳开这些日期
案例:
NthIncludedDayTrigger trigger = new NthIncludedDayTrigger("MyTrigger", Scheduler.DEFAULT_GROUP);
trigger.setN(15);
trigger.setIntervalType(NthIncludedDayTrigger.INTERVAL_TYPE_MONTHLY);
注意: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,精度是【天】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.");
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.");
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.");
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调度器
// 设置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调度器
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);
配置文件中增加 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/>
<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>
@Component
public class MyAnnoJob {
@Scheduled(cron = "*/5 * * * * ?")
// 每隔5秒执行一次
public void test() throws Exception {
System.out.println("Spring集成Quartz 使用 Annotation的方式......");
}
}
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");
}
}
@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 "";
}
属性类型 | 属性 | 属性说明 |
---|---|---|
String | cron | cron的表达式 |
String | zone | cron表达式将被解析的时区 |
long | fixedDelay | 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法 |
String | fixedDelayString | 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法 |
long | fixedRate | 在调用之间以固定的时间段执行带注释的方法 |
String | fixedRateString | 在调用之间以固定的时间段执行带注释的方法 |
long | initialDelay | 在首次执行 fixedRate 或 fixedDelay 任务之前要延迟的毫秒数 |
String | initialDelayString | 在首次执行 fixedRate 或 fixedDelay 任务之前要延迟的毫秒数 |
@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);
}
);
}
}
//@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();
}
}