Java多线程编程5--定时器Timer的使用

时间:2022-01-27 23:33:14

    定时/计划功能在移动开发领域使用较多,比如Android技术。定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和线程技术还是有非常大的关联的。
    在JDK库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。
    Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类
    执行计划任务的代码要放人TimerTask的子类中,因为TimerTask是一个抽象类。

schedule (TimerTask task, Date time)
    该方法的作用是在指定的日期执行一次某一任务。

schedule(TimerTack task, Date firstTime, long period)
    该方法的作用是在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务。

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

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

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

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

    该方法的作用是在指定的日期执行一次某一任务。

1.1、执行任务的时间晚于当前时间:在未来执行的效果

public class Run {
//private static Timer timer = new Timer(true);
  private static Timer timer = new Timer();
public static class MyTask extends TimerTask {

@Override
public void run() {
System.out.println("运行了!时间为: " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2016-5-8 11:44:11";
Date date = sdf.parse(dateString);
System.out.println("字符串时间为: " + date.toLocaleString()
+", 当前时间为: "+ new Date().toLocaleString() );

timer.schedule(task, date);
}
}
字符串时间为: 2016-5-8 11:44:11, 当前时间为: 2016-5-8 11:43:44
运行了!时间为: Sun May 08 11:44:11 CST 2016
直到时间到指定的时间,才执行”运行了。。。“

  任务虽然执行完了,但进行还未销毁,呈红色状态,为什么会出现这样的情况?
  在创建Timer对象时,JDK源代码为

    public Timer() {
this("Timer-" + serialNumber());
}
    此构造方法调用的是如下构造方法
    public Timer(String name) {
thread.setName(name);
thread.start();
}
    查看构造方法可以得知,创建一个Timer就是启动一个新的线程,这个新启动的线程并不是守护线程,它一直在运行。

    下一步将新创建的Timer改成守护线程,就是将Timer(true)就行了。结果

字符串时间为: 2016-5-8 15:40:11, 当前时间为: 2016-5-8 15:39:40
  程序运行后立即结束当前的进程,并且TimerTask中的任务不再被执行,因为进程已经结束了。

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

    如果执行任务的时间早于当前时间,则立即执行task任务。

public class RunTimerIsDaemon {
private static Timer timer = new Timer();

public static class MyTask extends TimerTask {

@Override
public void run() {
System.out.println("运行了!时间为: " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-5-8 15:40:11"; //这里把时间设为过去时
Date date = sdf.parse(dateString);
System.out.println("字符串时间为: " + date.toLocaleString()
+", 当前时间为: "+ new Date().toLocaleString() );

timer.schedule(task, date);
}
}
字符串时间为: 2015-5-8 15:40:11, 当前时间为: 2016-5-8 15:55:57
运行了!时间为: Sun May 08 15:55:57 CST 2016
立即执行task任务

1.3、多个TImerTask任务及延时的测试

    Timer中允许有多个TimerTask任务

public class Run {
private static Timer timer = new Timer();

public static class MyTask1 extends TimerTask {

@Override
public void run() {
System.out.println("task1运行了!时间为: " + new Date());
}
}

public static class MyTask2 extends TimerTask {

@Override
public void run() {
System.out.println("task2运行了!时间为: " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask1 task1 = new MyTask1();
MyTask2 task2 = new MyTask2();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateString1 = "2016-5-8 16:29:11"; //这里把时间设为未来时
String dateString2 = "2016-5-8 16:29:20"; //这里把时间设为未来时
Date date1 = sdf.parse(dateString1);
Date date2 = sdf.parse(dateString2);

System.out.println("字符串时间为: " + date1.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());
System.out.println("字符串时间为: " + date2.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());

timer.schedule(task1, date1);
timer.schedule(task2, date2);

}
}
字符串时间为: 2016-5-8 16:29:11, 当前时间为: 2016-5-8 16:29:10
字符串时间为: 2016-5-8 16:29:20, 当前时间为: 2016-5-8 16:29:10
task1运行了!时间为: Sun May 08 16:29:11 CST 2016
task2运行了!时间为: Sun May 08 16:29:20 CST 2016
     TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟。看如下示例
public class Run {
private static Timer timer = new Timer();

public static 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();
}
}
}

public static 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) throws ParseException {
MyTask1 task1 = new MyTask1();
MyTask2 task2 = new MyTask2();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateString1 = "2016-5-8 18:52:11"; //这里把时间设为未来时
String dateString2 = "2016-5-8 18:52:20"; //这里把时间设为未来时
Date date1 = sdf.parse(dateString1);
Date date2 = sdf.parse(dateString2);

System.out.println("字符串时间为: " + date1.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());
System.out.println("字符串时间为: " + date2.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());

timer.schedule(task1, date1);
timer.schedule(task2, date2);

}
}
字符串时间为: 2016-5-8 18:52:11, 当前时间为: 2016-5-8 18:51:40
字符串时间为: 2016-5-8 18:52:20, 当前时间为: 2016-5-8 18:51:40
1 begin 运行了!时间为: Sun May 08 18:52:11 CST 2016
1   end 运行了!时间为: Sun May 08 18:52:31 CST 2016
2 begin 运行了!时间为: Sun May 08 18:52:31 CST 2016
运行了!时间为 : Sun May 08 18:52:31 CST 2016
2   end 运行了!时间为: Sun May 08 18:52:31 CST 2016
可以看到任务2的运行时间被延迟了。
------------------------------------------------------------------------------------------------

2、方法schedule(TimerTack task, Date firstTime, long period)的测试

    该方法的作用是在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务。

2.1、计划时间晚于当前时间:在未来执行的效果

public class Run {
private static Timer timer = new Timer();

public static class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为: " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2016-5-8 19:20:11"; //这里把时间设为过去时
Date date = sdf.parse(dateString);
System.out.println("字符串时间为: " + date.toLocaleString()
+", 当前时间为: "+ new Date().toLocaleString() );

timer.schedule(task, date, 4000); //每隔4s运行一次TimerTask任务
}
}
字符串时间为: 2016-5-8 19:20:11, 当前时间为: 2016-5-8 19:20:05
运行了!时间为: Sun May 08 19:20:11 CST 2016
运行了!时间为: Sun May 08 19:20:15 CST 2016
运行了!时间为: Sun May 08 19:20:19 CST 2016
运行了!时间为: Sun May 08 19:20:23 CST 2016
。。。。。。
    每隔4s运行一次TimerTask任务,并且是无限期地重复执行。

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

    如果计划时间早于当前时间,则立即执行task任务。把上面的程序再执行一次就达到了想要的时间,因为dateString已经是过去时了。

字符串时间为: 2016-5-8 19:20:11, 当前时间为: 2016-5-8 19:25:48
运行了!时间为: Sun May 08 19:25:48 CST 2016
运行了!时间为: Sun May 08 19:25:52 CST 2016
运行了!时间为: Sun May 08 19:25:56 CST 2016
。。。。。。
可以看到是立即执行task任务

    如果任务的执行时间(用sleep可以测试)长于预期时间,则任务会被延迟,但还是一个一个顺序执行。

2.3、TimerTask类的cancel()方法

    TimerTask类中的cancel()方法的作用是将自身从任务队列中清除。

public class Run {
private static Timer timer = new Timer();

public static class MyTask1 extends TimerTask {
@Override
public void run() {
System.out.println("任务1运行了!时间为: " + new Date());
this.cancel(); //将自身从任务队列中清除
}
}

public static class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("任务2运行了!时间为 : " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask1 task1 = new MyTask1();
MyTask2 task2 = new MyTask2();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2016-5-8 19:20:11"; //这里把时间设为过去时
Date date = sdf.parse(dateString);
System.out.println("字符串时间为: " + date.toLocaleString()
+", 当前时间为: "+ new Date().toLocaleString() );

timer.schedule(task1, date, 4000);
timer.schedule(task2, date, 4000);
}
}
字符串时间为: 2016-5-8 19:20:11, 当前时间为: 2016-5-8 19:39:45
任务1运行了!时间为: Sun May 08 19:39:45 CST 2016
任务2运行了!时间为 : Sun May 08 19:39:45 CST 2016
任务2运行了!时间为 : Sun May 08 19:39:49 CST 2016
任务2运行了!时间为 : Sun May 08 19:39:53 CST 2016
TimerTask类的cancel()方法是将自身从任务队列中被移除,其他任务不受影响。

2.4、Timer类的cancel()方法

    和TimerTask类中的cancel()方法清除自身不同,Timer类中的cancel()方法的作用是将任务队列中的全部任务清空。将上面的MyTask1修改为如下:

    public static class MyTask1 extends TimerTask {
@Override
public void run() {
System.out.println("任务1运行了!时间为: " + new Date());
timer.cancel(); //将任务队列中的全部任务清空
}
}
字符串时间为: 2016-5-8 19:20:11, 当前时间为: 2016-5-8 19:45:49
任务1运行了!时间为: Sun May 08 19:45:49 CST 2016
全部任务被清除,并且进程被销毁,按钮由红包变成灰色。

    但也要注意:Timer类中的cancel()有时并一定人停下执行计划任务,而是正常执行,因为有时会争抢不到queue锁。

----------------------------------------------------

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

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

public class Run {
private static Timer timer = new Timer();

public static class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("运行了!时间为: " + new Date());
}
}

public static void main(String[] args) throws ParseException {
MyTask task = new MyTask();
System.out.println("当前时间为: " + new Date().toLocaleString());
timer.schedule(task, 4000); //延迟4s后才执行task
}
}
当前时间为: 2016-5-8 19:58:29
运行了!时间为: Sun May 08 19:58:34 CST 2016
延迟4s后才执行任务

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

    该方法的作用是以执行schedule(TimerTask task, long delay, long period)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务
示例:将上面的main方法里的:
    timer.schedule(task, 4000);
修改为
    timer.schedule(task, 4000,1000); //延迟4s后再以1s的间隔无限次执行task

当前时间为: 2016-5-8 20:05:59
运行了!时间为: Sun May 08 20:06:03 CST 2016
运行了!时间为: Sun May 08 20:06:04 CST 2016
运行了!时间为: Sun May 08 20:06:05 CST 2016
......
凡是使用方法中带有period参数的,都是无限循环执行TimerTask中的任务.

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

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

    延时的情况则没有区别,也就是使用schedule或scheduleAtFixedRate方法都是如果执行任务的时间被延时,那么下一次任务的执行时间参考的是上一次任务“结束”时的时间来计算。

5.1.测试schedule方法任务不延时

需要注意:下面的这个程序run里的睡眠时间要 小于  timer.schedule里的period时间

public class Run {
private static Timer timer = new Timer();
private static int runCount = 0;

public static 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) throws ParseException {
MyTask1 task1 = new MyTask1();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateString1 = "2016-5-8 20:23:11"; //这里时间随意,最好用过去时
Date date1 = sdf.parse(dateString1);
System.out.println("字符串1时间为: " + date1.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());

timer.schedule(task1, date1, 3000);
}
}
字符串1时间为: 2016-5-8 20:23:11, 当前时间为: 2016-5-8 20:23:35
1 begin 运行了!时间为: Sun May 08 20:23:35 CST 2016
1   end 运行了!时间为: Sun May 08 20:23:36 CST 2016
1 begin 运行了!时间为: Sun May 08 20:23:38 CST 2016
1   end 运行了!时间为: Sun May 08 20:23:39 CST 2016
1 begin 运行了!时间为: Sun May 08 20:23:41 CST 2016
1   end 运行了!时间为: Sun May 08 20:23:42 CST 2016
    控制台打印的结果证明,在不延时的情况下,如果执行任务的时间没有被延时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间。

5.2.测试schedule方法任务延时

    把上面程序任务里的睡眠时间修改成5000进行测试,结果为:

字符串1时间为: 2016-5-8 20:23:11, 当前时间为: 2016-5-8 20:28:29
1 begin 运行了!时间为: Sun May 08 20:28:29 CST 2016
1   end 运行了!时间为: Sun May 08 20:28:34 CST 2016
1 begin 运行了!时间为: Sun May 08 20:28:34 CST 2016
1   end 运行了!时间为: Sun May 08 20:28:39 CST 2016
1 begin 运行了!时间为: Sun May 08 20:28:39 CST 2016
1   end 运行了!时间为: Sun May 08 20:28:44 CST 2016
    从控制台打印的结果来看,如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算。

5.3.测试scheduleAtFixedRate方法任务不延时

  下面的这个程序run里的睡眠时间要 小于  scheduleAtFixedRate里的period时间

public class Run {
private static Timer timer = new Timer();
private static int runCount = 0;

public static 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) throws ParseException {
MyTask1 task1 = new MyTask1();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateString1 = "2016-5-8 20:23:11"; //这里时间随意,最好用过去时
Date date1 = sdf.parse(dateString1);
System.out.println("字符串1时间为: " + date1.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());

timer.scheduleAtFixedRate(task1, date1, 2000);
}
}
字符串1时间为: 2016-5-8 20:23:11, 当前时间为: 2016-5-8 20:32:44
1 begin 运行了!时间为: Sun May 08 20:32:44 CST 2016
1   end 运行了!时间为: Sun May 08 20:32:45 CST 2016
1 begin 运行了!时间为: Sun May 08 20:32:45 CST 2016
1   end 运行了!时间为: Sun May 08 20:32:46 CST 2016
1 begin 运行了!时间为: Sun May 08 20:32:46 CST 2016
从控制台打印的结果来看,如果执行任务的时间不延时,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算。

5.4.测试scheduleAtFixedRate方法任务延时

    把上面程序任务里的睡眠时间修改成5000进行测试,结果为:

字符串1时间为: 2016-5-8 20:23:11, 当前时间为: 2016-5-8 20:40:18
1 begin 运行了!时间为: Sun May 08 20:40:18 CST 2016
1   end 运行了!时间为: Sun May 08 20:40:23 CST 2016
1 begin 运行了!时间为: Sun May 08 20:40:23 CST 2016
1   end 运行了!时间为: Sun May 08 20:40:28 CST 2016
1 begin 运行了!时间为: Sun May 08 20:40:28 CST 2016
    从控制台打印的结果来看,如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算。

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

public class Run {
private static Timer timer = new Timer();

public static 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) throws ParseException {
MyTask1 task1 = new MyTask1();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateString1 = "2016-5-8 20:46:11"; //这里时间随意,最好用过去时
Date date1 = sdf.parse(dateString1);
System.out.println("字符串1时间为: " + date1.toLocaleString()
+ ", 当前时间为: " + new Date().toLocaleString());

timer.schedule(task1, date1, 2000);
}
}
字符串1时间为: 2016-5-8 20:46:11, 当前时间为: 2016-5-8 20:48:28
1 begin 运行了!时间为: Sun May 08 20:48:28 CST 2016
1   end 运行了!时间为: Sun May 08 20:48:28 CST 2016
1 begin 运行了!时间为: Sun May 08 20:48:30 CST 2016
1   end 运行了!时间为: Sun May 08 20:48:30 CST 2016
1 begin 运行了!时间为: Sun May 08 20:48:32 CST 2016
    时间”2016-5-8 20:46:11“到”2016-5-8 20:48:28“之间(如果dateString是未来时也一样是这段时差)之间的时间所对应的Task任务被 取消了,不执行了。这就是Task任务不追赶的情况。

5.6.验证scheduleAtFixedRate方法具有追赶执行性

将上面main方法里的schedule修改为scheduleAtFixedRate即可。结果为把两个时间段内所对应的Task伤”补充性“执行了,这就是Task任务的追赶的特性