在java中,定时计划任务功能主要使用的就是Timer对象,主要有如下技术点:
- 实现指定事件执行指定任务
- 实现按照指定周期执行任务
定时器Timer
Timer类的主要作用是设置计划执行的任务,而封装任务的类是TimerTask类,类声明如下:
public abstract class TimerTask implements Runnable{...}
可以看到TimerTask是一个抽象类,实现了Runnable接口,我们需要创建子类继承TimerTask抽象类,并且将要执行的任务放入这个子类中。
schedule(TimerTask task,Date time)
首先先来看一下Timer的最基本用法,schedule(TimerTask, Date)
,我们要先创建一个类继承TimerTask类,并重写run()方法,然后实例化这个类的对象,作为schedule(TimerTask, Date)方法的参数之一。另一个参数是我们需要TimerTask中任务执行的时间。另外,当两个任务都需要计划执行,并且是使用了同一个Timer实例
该方法的作用是在指定的日期到达时执行一次某一任务。这里分两种情况
- 执行任务的任务晚于当前时间,在未来执行
- 执行的时间早于当前时间,提前运行
在未来执行:
public class TimerTest {
private static class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("MyTimerTask runs and the time is : "+new Date());
}
}
public static void main(String[] args) throws ParseException {
MyTimerTask myTimerTask = new MyTimerTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 这里设置的时间是在未来,此时时间到达该点的时候,会执行
String dateString = "2020-01-06 20:36:00";
Date date = sdf.parse(dateString);
Timer timer = new Timer();
timer.schedule(myTimerTask, date);
}
}
多个TimerTask任务延时测试
由于TimerTask是以队列的方式一个一个被顺序执行的,所以执行时间可能和预期的时间不一致,如果前面的任务消耗时间较长,则后面的任务运行的时间也会被延迟。
public class Test {
private static class MyTimerTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("MyTimerTask1 begin runs and the time is : "+new Date());
Thread.sleep(5000);
System.out.println("MyTimerTask1 end runs and the time is : "+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class MyTimerTask2 extends TimerTask {
@Override
public void run() {
System.out.println("MyTimerTask2 runs and the time is : "+new Date());
}
}
public static void main(String[] args) throws ParseException {
MyTimerTask1 myTimerTask1 = new MyTimerTask1();
MyTimerTask2 myTimerTask2 = new MyTimerTask2();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2020-01-06 20:49:00";// MyTimerTask1的起始执行时间
String dateString2 = "2020-01-06 20:49:03";// MyTimerTask2的起始执行时间
Date date1 = sdf.parse(dateString1);
Date date2 = sdf.parse(dateString2);
Timer timer = new Timer();
timer.schedule(myTimerTask1, date1);
timer.schedule(myTimerTask2, date2);
}
}
输出:
MyTimerTask1 begin runs and the time is : Mon Jan 06 20:49:02 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 20:49:07 CST 2020
MyTimerTask2 runs and the time is : Mon Jan 06 20:49:07 CST 2020
可以看到这里由于MyTimerTask1 执行需要花费5秒,那么将会影响task2的执行时间,task2以此时间为基准,向后延迟执行,因为task是被放入到队列中,一个一个顺序执行的,同步执行!
schedule(TimerTask task,Date firstTime,long period)
该方法的作用是在指定的日期之后,按照指定的间隔周期性的无限循环执行某一任务。
TimerTask.cancel()和Timer.cancel()
TimerTask.cancel()
是用来将本任务从Timer的执行队列中删除掉,使之不再继续执行;
Timer.cancel()
是将本Timer的所有任务都撤销,不再继续执行任何任务,同时进程会被销毁,程序结束。
但是有时调用cancel()方法的线程可能没有第一时间竞争到queue的对象锁,那么任务可能会完成执行,没有被及时销毁。
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
scheduleAtFixedRate
和schedule
一样,在当前任务延时的情况,上一个任务执行完毕后当前任务立即执行,两个方法的处理方式都一样
所以schedule和scheduleAtFixedRate主要的区别只在于不延时的情况:
- schedule方法:如果执行的任务时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的开始时间来计算
- scheduleAtFixedRate方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务结束的时间来计算 ????
public class Test {
private static Timer timer = new Timer();
private static class MyTimerTask1 extends TimerTask {
private int count = 0;
@Override
public void run() {
try {
System.out.println("MyTimerTask1 begin runs and the time is : "+new Date());
Thread.sleep(1000);
System.out.println("MyTimerTask1 end runs and the time is : "+new Date());
count++;
if (count > 3) {
timer.cancel(); // 停止
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws ParseException {
MyTimerTask1 myTimerTask1 = new MyTimerTask1();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2020-01-06 21:03:50";// MyTimerTask1的起始执行时间
Date date = sdf.parse(dateString);
timer.schedule(myTimerTask1, date,3000); //从"2020-01-06 21:03:50"开始,每隔3秒执行一次
}
}
输出: 可以看到,在没有延时情况下,下一次任务开始时间 = 上一层任务开始时间 + 延时delay
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:03:50 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:03:51 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:03:53 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:03:54 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:03:56 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:03:57 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:03:59 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:04:00 CST 2020
schedule 换成 scheduleAtFixedRate 结果时一样的????
scheduleAtFixedRate 追赶性
把上面代码中任务的执行时间dateString换成当前时间之前: 当前时间是2020-01-06 21:25:09
public static void main(String[] args) throws ParseException {
MyTimerTask1 myTimerTask1 = new MyTimerTask1();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2020-01-06 21:25:00";// MyTimerTask1的起始执行时间
Date date = sdf.parse(dateString);
timer.schedule(myTimerTask1, date,3000);
}
输出:
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:25:10 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:25:11 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:25:13 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:25:14 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:25:16 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:25:17 CST 2020
可以发现,在当前时间和执行时间的缝隙里,任务没有执行,在从当前时间开始之后,还是按照3s执行一次。
再来看看 把schedule方法 换成 scheduleAtFixedRate:
前几行:
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:31:54 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:31:55 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:31:55 CST 2020 // 不等待,直接执行
MyTimerTask1 end runs and the time is : Mon Jan 06 21:31:56 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:31:56 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:31:57 CST 2020
看到 虽然任务还是从当前时间开始执行,但是没有停留3s执行下一次,而是执行完这一次,马上开始执行下一次。这就是一个追赶的过程,它 在把当前时间和执行时间的缝隙里欠的任务 补回来。
等到补完了,就正常按照 3s 执行一次来:后几行如下:
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:32:30 CST 2020
MyTimerTask1 end runs and the time is : Mon Jan 06 21:32:31 CST 2020
MyTimerTask1 begin runs and the time is : Mon Jan 06 21:32:33 CST 2020 // 注意是33才开始执行
MyTimerTask1 end runs and the time is : Mon Jan 06 21:32:34 CST 2020