定时器Timer

定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和其它线程技术上还是有非常大的关联的。

定时器Timer的使用

在JDK库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。Timer类的方法列表如图1所示。
Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,类结构如图2所示。执行计划任务的代码要放入TimerTask的子类中,因为因为TimerTask是一个抽象类。



图1 类Timer的方法列表


图2 类TimerTask类相关的信息

方法schedule(TimerTask task,Date time)的测试

该方法的作用是在指定的日期执行一次某一任务。
1 执行任务的时间晚于当前的时间:在未来执行的效果

代码清单1 在未来执行的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Run1 {
private static Timer timer = new Timer();
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2017-11-13 23:46:38";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间" + dateRef.toLocaleString() + " 当前时间"
+ new Date().toLocaleString());
timer.schedule(task, dateRef);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

程序运行后的结果如图3所示。任务虽然执行完了,但进程还未销毁,为什么会出现这样的情况?



图3 运行结果

在创建Timer对象时,JDK源码如下:

1
2
3
public Timer(){
this("Timer-"+serialNumber());
}

此构造方法调用的是如下构造方法:

1
2
3
4
public Timer(String name){
thread.setName(name);
thread.start();
}

查看构造方法可以得知,创建一个Timer就是启动一个新的线程,这个新启动的线程并不是守护线程,它一直在运行。
查看构造方法可以得知,创建一个Timer就是启动一个新的线程,这个新启动的线程并不是守护线程,它一直在运行。
代码清单2 Timer的守护线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Run1TimerIsDaemon {
private static Timer timer = new Timer(true);
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2017-11-13 22:07:16";
//Timer timer=new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:" + new Date().toLocaleString());
timer.schedule(task, dateRef);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

程序运行后迅速结束当前的进程,并且TimerTask中的任务不再被运行,因为线程已经结束了。



图4 守护线程创建成功进程退出

2 计划时间早于当前时间:提前运行的效果

如果执行任务的时间早于当前时间,则立即执行task任务。将代码清单2中//Timer timer=new Timer();注释去掉。



图5 立即执行task任务

3 多个TimerTask任务及延时的测试

Timer中允许有多个TimerTask任务。
代码清单3 多个TimerTask任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Run2 {
private static Timer timer = new Timer();
static public class MyTask1 extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
static public class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
MyTask2 task2 = new MyTask2();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 10:21:48";
String dateString2 = "2017-11-14 10:21:49";
Date dateRef1 = sdf1.parse(dateString1);
Date dateRef2 = sdf2.parse(dateString2);
System.out.println("字符串1时间" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
System.out.println("字符串2时间" + dateRef2.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task1, dateRef1);
timer.schedule(task2, dateRef2);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

程序运行结果如图6所示。



图6 一个Timer中可以运行多个TimerTask

TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟。
代码清单4 任务运行被延迟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Run2Later {
private static Timer timer = new Timer();
static public class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("1 begin 运行了!时间为:" + new Date());
Thread.sleep(20000);
System.out.println("1 end 运行了!时间为:" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static public class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("2 begin 运行了!时间为:" + new Date());
System.out.println("运行了!时间为:" + new Date());
System.out.println("2 end 运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
MyTask2 task2 = new MyTask2();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 10:30:18";
String dateString2 = "2017-11-14 10:30:28";
Date dateRef1 = sdf1.parse(dateString1);
Date dateRef2 = sdf2.parse(dateString2);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
System.out.println("字符串2时间:" + dateRef2.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task1, dateRef1);
timer.schedule(task2, dateRef2);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

由于task1需要用时20秒执行完成任务,task1开始的时间是2017-11-14 10:30:18,那么将要影响task2的计划任务的时间,task2以此时间为基准,向后延迟20秒,task2在11:33:20执行任务。因为Task是被放入队列中的,得一个一个顺序运行

程序运行结果如图6所示。



图7 任务2的运行时间被延迟了

方法schedule(TimerTask task,Date time,long period)的测试

该方法的作用是指在指定日期之后,按指定的间隔周期性地无限循环执行某一任务。
1 计划时间晚于当前时间:在未来执行的效果。

代码清单5 在未来的时间开始循环执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Run {
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2017-11-14 10:46:11";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task, dateRef, 4000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图8所示,每隔4秒运行一次TimerTask,并且无限期地重复执行。



图8 运行结果

2 计划时间早于当前时间:提前运行的效果

如果计划时间早于当前时间,则立即执行task。
运行代码清单5中程序,运行结果如图9所示。



图9 立即执行task任务

3 任务执行时间被延迟时

代码清单6 任务执行时间被延迟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Run2_1 {
static public class MyTaskA extends TimerTask {
@Override
public void run() {
try {
System.out.println("A运行了!时间为:" + new Date());
Thread.sleep(5000);
System.out.println("A结束了!时间为:" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyTaskA taskA = new MyTaskA();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-3-19 14:14:00";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(taskA, dateRef, 4000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图10所示,任务被延时了但还是一个一个顺序运行。



图10 运行结果

4 TimerTask类的cancel()方法

TimerTask类中的cancel()方法的作用是将自身从任何队列中清楚。
代码清单7 TimerTask类的cancel()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Run2 {
static public class MyTaskA extends TimerTask {
@Override
public void run() {
System.out.println("A运行了!时间为:" + new Date());
this.cancel();
}
}
static public class MyTaskB extends TimerTask {
@Override
public void run() {
System.out.println("B运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTaskA taskA = new MyTaskA();
MyTaskB taskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2014-10-12 09:12:00";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(taskA, dateRef, 4000);
timer.schedule(taskB, dateRef, 4000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

TimerTask类的cancel()方法是将自身从任务队列中被移除,其他任务不受影响。



图11 TimerTaskA仅运行一次后被清楚了

5 Timer类的cancel()方法

和TimerTask类中的cancel()方法清楚自身不同,Timer类中的cancel()方法的作用是将任务队列中的全部任务清空。
代码清单8 Timer类的cancel()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Run3 {
private static Timer timer = new Timer();
static public class MyTaskA extends TimerTask {
@Override
public void run() {
System.out.println("A运行了!时间为:" + new Date());
timer.cancel();
}
}
static public class MyTaskB extends TimerTask {
@Override
public void run() {
System.out.println("B运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTaskA taskA = new MyTaskA();
MyTaskB taskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2017-11-14 11:38:23";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(taskA, dateRef, 4000);
timer.schedule(taskB, dateRef, 4000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图12所示,全部任务被清除,并且进程被销毁,按钮有红色变为灰色。



图12 进程被清空

6 Timer的cancel()方法注意事项

Timer类中的cancel()方法有时并不一定会停止执行计划任务,而是正常执行。
代码清单9 cancel不一定能停止执行计划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Run4 {
static int i = 0;
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("正常执行了" + i);
}
}
public static void main(String[] args) {
while (true) {
try {
i++;
Timer timer = new Timer();
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
String dateString = "2017-11-14 11:54:12";
Date dateRef = sdf.parse(dateString);
timer.schedule(task, dateRef);
timer.cancel();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}

运行结果如图13所示。这是因为Timer类中的cancel()方法有时并没有争抢到queue锁,所以TimerTask 正常运行。



图13 并没有停止

方法schedule(TimerTask task,long delay)的测试

该方法的作用是以执行schedule(TimerTask task,long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。
代码清单10 延迟执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Run {
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
MyTask task = new MyTask();
Timer timer = new Timer();
System.out.println("当前时间?" + new Date().toLocaleString());
timer.schedule(task, 7000);
}
}

任务task被延迟7秒执行,如图14所示。



图14 运行结果

方法schedule(TimerTask task,long delay,long period)的测试

该方法的作用是以执行schedule(TimerTask task,long delay,long period)方法当前的时间Wie参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
代码清单11 无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Run {
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
MyTask task = new MyTask();
Timer timer = new Timer();
System.out.println("当前时间"+new Date().toLocaleString());
timer.schedule(task, 3000,5000);
}
}

凡是使用方法带有period参数的,都是无限循环执行TimerTask中的任务。



图15 循环运行

方法scheduleAtFixedRate(TimerTask task,Date firstTime,long period)的测试

方法schedule和方法scheduleAtFixedRate都会按顺序执行,所以不要考虑非线程安全的情况。
方法schedule和scheduleAtFixedRate主要的区别只在于不延时的情况。
使用schedule方法:如果执行任务的时间没有被延迟时,那么下一次任务的执行时间参考的是上一次任务的“开始”时的时间来计算。
使用scheduleAtFixedRate方法:如果执行任务的时间没有被延迟时,那么下一次任务的执行时间参考的是上一次任务的“结束”时的时间来计算。
延时的情况则没有区别,也就是使用schedule或scheduleAtFixedRate方法都是如果执行任务的时间被延迟时,那么下一次任务的执行时间参考的是上一次任务“结束”时的时间来计算。

1 测试schedule方法任务不延时

代码清单12 测试schedule方法任务不延时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Run1 {
private static Timer timer = new Timer();
private static int runCount = 0;
static public class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("1 begin 运行了!时间为:" + new Date());
Thread.sleep(1000);
System.out.println("1 end 运行了!时间为:" + new Date());
runCount++;
if (runCount == 5) {
timer.cancel();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 13:43:16";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task1, dateRef1, 3000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

控制台打印的结果证明,在不延时的情况下,如果执行任务的时间没有被延迟时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间。



图16 没有延时的运行效果

2 测试schedule方法任务超时

代码清单13 schedule方法任务超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Run2 {
private static Timer timer = new Timer();
private static int runCount = 0;
static public class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("1 begin 运行了!时间为:" + new Date());
Thread.sleep(5000);
System.out.println("1 end 运行了!时间为:" + new Date());
runCount++;
if (runCount == 5) {
timer.cancel();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 14:42:10";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task1, dateRef1, 2000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

程序运行结果如图17所示,从控制台打印的结果来看,如果执行任务的时间被延迟时,那么下一次的执行时间以上一次“结束”时的时间为参数来计算。



图17 任务延时的效果

3 测试scheduleAtFixedRate方法任务不延时

代码清单14 scheduleAtFixedRate方法任务不延时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Run3 {
private static Timer timer = new Timer();
private static int runCount = 0;
static public class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("1 begin 运行了!时间为:" + new Date());
Thread.sleep(2000);
System.out.println("1 end 运行了!时间为:" + new Date());
runCount++;
if (runCount == 5) {
timer.cancel();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 14:48:21";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.scheduleAtFixedRate(task1, dateRef1, 3000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图18所示,控制台打印的结果证明,如果执行任务的时间没有被延迟,则下一次执行任务的时间是上一次任务的开始时间加上delay时间。



图18 没有被延时的运行效果

4 测试scheduleAtFixedRate方法任务超时

代码清单15 scheduleAtFixedRate方法任务超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Run4 {
private static Timer timer = new Timer();
private static int runCount = 0;
static public class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("1 begin 运行了!时间为:" + new Date());
Thread.sleep(5000);
System.out.println("1 end 运行了!时间为:" + new Date());
runCount++;
if (runCount == 5) {
timer.cancel();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 15:01:06";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.scheduleAtFixedRate(task1, dateRef1, 2000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图19所示,如果执行任务的时间被延迟,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算。



图19 任务延时的运行效果

5 验证schedule方法不具有追赶执行性

代码清单16 schedule方法不具有追赶执行性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Run5 {
private static Timer timer = new Timer();
static public class MyTask1 extends TimerTask {
@Override
public void run() {
System.out.println("1 begin 运行了!时间为:" + new Date());
System.out.println("1 end 运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 15:13:43";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.schedule(task1, dateRef1, 5000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图20所示,时间“2017-11-14 15:13:43”到“2017-11-14 15:16:06”之间的时间所对应的Task任务被取消了,不执行了。这就是Task不追赶的情况。



图20 不追赶的情况

6 验证scheduleAtFixedRate方法具有追赶执行性
代码清单17 scheduleAtFixedRate方法具有追赶执行性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Run6 {
private static Timer timer = new Timer();
static public class MyTask1 extends TimerTask {
@Override
public void run() {
System.out.println("1 begin 运行了!时间为:" + new Date());
System.out.println("1 end 运行了!时间为:" + new Date());
}
}
public static void main(String[] args) {
try {
MyTask1 task1 = new MyTask1();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2017-11-14 15:20:59";
Date dateRef1 = sdf1.parse(dateString1);
System.out.println("字符串1时间:" + dateRef1.toLocaleString() + " 当前时间:"
+ new Date().toLocaleString());
timer.scheduleAtFixedRate(task1, dateRef1, 5000);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

运行结果如图21所示,两个时间段内所对应的Task被“补充性”执行了,这就是Task任务追赶执行的特性。



图21 追赶的情况

Java多线程编程核心技术源代码

Adhere to the original technology to share, your support will encourage me to continue to create!