三种定时方案
- Java自带的 timer 类,可以实现指定频率的任务调度,通过 timer.schedule() 启动,timer.cancel() 终止。
- 采用 Quartz 调度器实现。这是一个开源的专门用于定时任务调度的框架,就是有点复杂
- spring3.0后自带 ThreadPoolTaskScheduler,可以实现 Quartz 的大部分功能,且不需要额外引用 jar,支持注解和配置文件两种方式。
1. Timer
timer 是单线程执行,每当上一个任务执行完毕之后,才进行下一个判断。
一旦中途抛出一个异常,且未能被捕获,则整个定时器终止。
两个调度函数
- schedule 调度一个task,在delay(ms)后开始调度,每次任务执行完后,至少等待period(ms)后才开始调度
public void schedule(TimerTask task, Date time)
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, Date firstTime, long period)
public void schedule(TimerTask task, long delay, long period)
In fixed-delay execution, each execution is scheduled relative tothe actual execution time of the previous execution. If an executionis delayed for any reason (such as garbage collection or otherbackground activity), subsequent executions will be delayed as well.In the long run, the frequency of execution will generally be slightlylower than the reciprocal of the specified period (assuming the systemclock underlying Object.wait(long) is accurate). As aconsequence of the above, if the scheduled first time is in the past,it is scheduled for immediate execution.
在固定延迟执行中,每次执行都是相对于上一次执行的实际执行时间进行调度的。如果由于任何原因(如垃圾收集或其他后台活动)导致执行延迟,则后续执行也将延迟。执行频率通常略低于指定周期的倒数(我们假设 Object.wait(long) 所依据的系统时钟是准确的)。综上所述,如果第一次预计执行的时间已经过了,那么它会立即执行。
- scheduleAtFixedRate 调度一个task,在delay(ms)后开始调度,至少 等待 period(ms)调用一次(与任务执行耗时无关)
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
In fixed-rate execution, each execution is scheduled relative to thescheduled execution time of the initial execution. If an execution isdelayed for any reason (such as garbage collection or other backgroundactivity), two or more executions will occur in rapid succession to"catch up." In the long run, the frequency of execution will beexactly the reciprocal of the specified period (assuming the systemclock underlying Object.wait(long) is accurate).
在固定速率执行中,每次执行都是相对于初始执行的预定执行时间进行调度的。如果执行因任何原因而延迟(例如垃圾收集或其他后台活动),则会连续快速执行两次或更多次以“赶上”。从长远来看,执行频率将会是周期的倒数(我们假设 Object.wait(long) 所依据的系统时钟是准确的)。
两种使用方式
-
一个 timer 调度多个任务
当执行时间小于 period 时,两个函数表现一致,会在每次任务执行完毕后,判断从调度开始到现在经过的时间是否大于 period,如果是则继续调度,否则等待时间到了 period 才再次调度。
如果执行时间大于 period,scheduleAtFixedRate 的优先级大于schedule。 -
一个 timer 仅调度一个任务
两个函数表现一致,都是在每次任务执行完毕后,判断从调度开始到现在经过的时间是否大于 period,如果是则继续调度,否则等待时间到了 period 才再次调度。
注意:由于不是外部因素导致的延迟,因此在如下两种调度方式中, scheduleAtFixedRate 均不会有追赶的现象。
终止定时器
- timer.cancel() 终止定时器
- timerTask.cancel() 终止定时器中的某个任务
- timer.purge() 从计时器的任务队列中移除所有已取消的任务,并返回移除的个数
测试用例
- 一个 timer 调度多个任务
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
//调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度
long delay = 1000;
long period = 2000;
long cost = 3000;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("schedule: " + sdf.format(new Date()));
try {
Thread.sleep(cost);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("schedule end ! ! !");
}
}, delay, period);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("scheduleAtFixedRate: " + sdf.format(new Date()));
try {
Thread.sleep(cost);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("scheduleAtFixedRate end …… ……");
}
}, delay, period);
}
(1)period = 2s,cost = 3s 的情况
(2)period = 2s,cost = 1s 的情况
- 一个 timer 调度一个任务
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
//调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度
long delay = 1000;
long period = 2000;
long cost = 1000;
Timer timer1 = new Timer();
Timer timer2 = new Timer();
timer1.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("schedule: " + sdf.format(new Date()));
try {
Thread.sleep(cost);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, delay, period);
timer2.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("scheduleAtFixedRate: " + sdf.format(new Date()));
try {
Thread.sleep(cost);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, delay, period);
(1)period = 2s,cost = 3s 的情况
(2)period = 2s,cost = 1s 的情况
2. Quartz
要使用定时任务需要在启动类(@SpringBootApplication)上,加上启用定时任务的注解(@EnableScheduling)。然后在组件类(@Component)的待执行的方法上加上执行规则注解(@Scheduled)。程序会根据 @Scheduled 所提供的信息定时执行该方法。
Scheduled 参数
参数名 | 含义 |
---|---|
cron | 表达式 |
zone | 时区,默认为本地时区 TimeZone.getDefault() |
fixedDelay | 上次任务执行完后 N 秒继续执行 |
fixedDelayString | |
fixedRate | 每 N 秒执行一次 |
fixedRateString | |
initialDelay | 初始延迟 N 秒后执行 |
initialDelayString |
在线 Cron 表达式生成器
cron 规则
执行定时任务
@Component
@EnableScheduling
public class ScheduleTask {
private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
private String getTime() {
return sdf.format(new Date());
}
/** 从第0秒开始,每隔5秒执行一次 */
@Scheduled(cron = "0/5 * * * * *")
public void run1() {
System.out.println(getTime() + " cron");
}
/** 立即执行,然后每隔3秒执行一次 */
@Scheduled(fixedRate = 3000)
public void run2() {
System.out.println(getTime() + " fixedRate = 3000");
}
/** 立即执行,然后在上次执行完毕后,过2秒再次执行 */
@Scheduled(fixedDelay = 2000)
public void run3() {
System.out.println(getTime() + " fixedDelay = 2000");
}
/** 初始时延迟1秒后执行,后续每次都在上一次执行完3秒后再执行 */
@Scheduled(initialDelay = 1000, fixedDelay = 3000)
public void run4() {
System.out.println(getTime() + " initialDelay = 1000, fixedDelay = 3000");
}
}
关闭定时器
3. ThreadPoolTaskScheduler
它可以使用 cron 表达式,因此能实现 Quartz 的所有功能。
和 Timer 的区别是
- ThreadPoolTaskScheduler 是基于多线程的,而 Timer 是基于单线程的。
- 当有其中一个任务,有未捕获的错误被抛出时,因为 Timer 是单线程的,所以会导致整个定时器终止;而对 ThreadPoolTaskScheduler 的影响,仅仅是该任务的本次执行终止而已。
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
//设置线程池大小
scheduler.setPoolSize(10);
//设置线程名前缀
scheduler.setThreadNamePrefix("task-");
//设置关闭前最多等几秒
scheduler.setAwaitTerminationSeconds(10);
//设置等待任务执行完毕后再关闭
scheduler.setWaitForTasksToCompleteOnShutdown(true);
ScheduledFuture<?> future = scheduler.schedule(()->{
System.out.println("ThreadPoolTaskScheduler: " + sdf.format(new Date()));
throw new RuntimeException("");
},
//cron表达式的含义是,2秒执行一次
new CronTrigger("0/2 * * * * ? "));
//是否可以中断正在执行的任务
//boolean mayInterruptIfRunning = true;
//终止任务
//future.cancel(mayInterruptIfRunning);