在JDK中Timer类主要用于负责计划任务,也就是说在某个时间点开始执行一个计划好的任务。
Timer类中的方法列表如下:
serialNumber()
Timer():默认构造方法;
Timer(boolean):参数表示thread是否为守护线程
Timer(String):参数为线程名
Timer(String, boolean)
schedule(TimerTask, long):参数为计划相对于当前的延迟时间
schedule(TimerTask, Date):指定日期时间开始执行
schedule(TimerTask, long, long):参数为延迟时间和时间间隔
schedule(TimerTask, Date, long)
scheduleAtFixedRate(TimerTask, long, long)
scheduleAtFixedRate(TimerTask, Date, long)
sched(TimerTask, long, long)
cancel()
purge()
Timer类的主要作用是设置计划执行的任务,而封装任务的类是TimerTask类,类声明如下:
public abstract class TimerTask implements Runnable{...}
可以看到TimerTask是一个抽象类,实现了Runnable接口,我们需要创建子类继承TimerTask抽象类,并且将要执行的任务放入这个子类中。
一、schedule多个任务计划执行
下面依次介绍TimerTask类中的方法
首先先来看一下Timer的最基本用法,schedule(TimerTask, Date),我们要先创建一个类继承TimerTask类,并重写run()方法,然后实例化这个类的对象,作为schedule(TimerTask, Date)方法的参数之一。另一个参数是我们需要TimerTask中任务执行的时间。另外,当两个任务都需要计划执行,并且是使用了同一个Timer实例,而第一个执行的任务有需要耗费较长的时间时,则第二个任务会延迟进行,当第一个任务执行后第二个任务马上开始执行,两个线程不会异步执行。
public class Run1 { private static Timer timer = new Timer(); static public class MyTask extends TimerTask{ @Override public void run() { try { System.out.println("任务1执行时间为:"+new Date()); Thread.sleep(5000); }catch(InterruptedException e) { e.printStackTrace(); } } } static public class MyTask1 extends TimerTask{ @Override public void run() { System.out.println("任务2执行时间为:"+new Date()); } } public static void main(String args[]) { try { MyTask mt = new MyTask(); MyTask1 mt1 = new MyTask1(); SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); String dateString = "2018-05-24 13:50:50"; String dateString1 = "2018-05-24 13:50:53"; Date dateRef = sdf.parse(dateString); Date dateRef1 = sdf.parse(dateString1); System.out.println("设置的时间:"+dateRef.toLocaleString()+" 设置的时间1:"+dateRef1.toLocaleString()+" 当前时间"+new Date().toLocaleString()); timer.schedule(mt, dateRef); timer.schedule(mt1, dateRef1); }catch(ParseException e) { e.printStackTrace(); } } }
执行结果如下:
/*
设置的时间:2018-5-24 13:50:50 设置的时间1:2018-5-24 13:50:53 当前时间2018-5-24 13:49:28
任务1执行时间为:Thu May 24 13:50:50 CST 2018
任务2执行时间为:Thu May 24 13:50:55 CST 2018
*/
可以看到,两个任务之间计划执行时间相差3秒,但是第一个任务要5秒才能执行完 ,所以第二个任务在原定时间基础上推迟了两秒执行。如果需要两个任务都准时执行的话,则要为第二个任务单独创建一个Timer实例,这样两个任务就可以异步执行。
查看Timer类的jdk源码可以看到:
public Timer(String name) {
thread.setName(name);
thread.start();
}
当我们实例化一个Timer的时候,实际上是将Timer类中的一个thread变量启动,这个线程会一直运行,并且在执行完相关的任务后依然继续运行下去。当我们使用Timer的构造方法Timer(boolean isDaemon)时,我们可以将这个线程设置为守护线程,这样thread就会提前结束,并且可能会在计划执行的任务还没开始执行前就结束。
ps:当设置计划时间早于当前时间时,任务立即执行。
二、其他方法介绍
1、schedule(TimerTask task, Date firstTime, long period)
该方法的作用是使一个指定的任务在指定日期开始之后的时间内,以参数period为时间间隔,定期地反复执行。同样的,当计划时间早于当前时间时,任务立即开始执行;当任务需要执行的时间较长,并且大于设置的时间间隔period时,任务会延期执行,也就是说时间间隔将会变长。
实例代码如下:
public class Run2 { private static Timer timer = new Timer(); static public class MyTask extends TimerTask{ @Override public void run() { try { System.out.println("任务1执行时间为:"+new Date()); Thread.sleep(1000); }catch(InterruptedException e) { e.printStackTrace(); } } } public static void main(String args[]) { try { MyTask mt = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); String dateString = "2018-05-24 15:22:59"; Date dateRef = sdf.parse(dateString); System.out.println("设置的时间:"+dateRef.toLocaleString()+" 当前时间"+new Date().toLocaleString()); timer.schedule(mt, dateRef, 2000); }catch(ParseException e) { e.printStackTrace(); } } }
运行结果:
/*
设置的时间:2018-5-24 15:22:59 当前时间2018-5-24 15:22:18
任务1执行时间为:Thu May 24 15:22:59 CST 2018
任务1执行时间为:Thu May 24 15:23:01 CST 2018
任务1执行时间为:Thu May 24 15:23:03 CST 2018
任务1执行时间为:Thu May 24 15:23:05 CST 2018
*/可以看到,指定的TimerTask任务每隔两秒执行一次,然后我们将代码中的Thread.sleep(1000)改为Thread.sleep(5000),运行结果如下:
/*
设置的时间:2018-5-24 15:20:59 当前时间2018-5-24 15:20:59
任务1执行时间为:Thu May 24 15:20:59 CST 2018
任务1执行时间为:Thu May 24 15:21:04 CST 2018
任务1执行时间为:Thu May 24 15:21:09 CST 2018
任务1执行时间为:Thu May 24 15:21:14 CST 2018
任务的执行出现了延迟,时间间隔变为了5秒。
2、TimerTask.cancel()和Timer.cancel()
TimerTask.cancel是用来将本任务从Timer的执行队列中删除掉,使之不再继续执行;Timer.cancel()是将本Timer的所有任务都撤销,不再继续执行任何任务,同时进程会被销毁,程序结束。但是有时调用cancel()方法的线程可能没有第一时间竞争到queue的对象锁,那么任务可能会完成执行,没有被及时销毁。
3、scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
scheduleAtFixedRate和schedule一样,都可以按规定时间按顺序执行规定的计划,并且不需要考虑线程安全的问题,他们之间的区别在于两者在面对多任务不延时的情况,处理结果会有区别。首先说明延时的情况,再多任务情况下,如果由于上一个任务执行时间过长而导致下一个任务延时,那么上一个任务执行完毕后下一个任务立即执行,两个方法的处理方式都一样,这时所设置的延时period看起来就没什么作用了,因为任务总是一个接一个的执行。
但是当period大于任务执行时间的时候,要注意的一点是,如果设置的开始执行时间早于实际运行的时间,两种方法都会将前面没有完成的工作补上,但是补上工作的方法有区别,这就涉及到两者之间的差异——追赶执行性。scheduleAtFixedRate是具备追赶执行性的,而schedule不具备这个性质。
我们用一个例子来解释一下什么叫做追赶执行性,假设有两个要工厂生产零件,生产任务相同,都是每天需要生产100个零件,生产完就停下来不继续生产,一共生产10天。当生产到第5天的时候,突然两个工厂所在的地区停电3天,没法继续生产。到了第8天来电之后又恢复生产,这个时候两个工厂会采取不同的生产方案。
工厂A采用了schedule方案,第8天开始继续生产第5天本应该生产的100零件,并且生产够100后停下,等到第9天再继续开始生产。这样由于停电3天,工厂A一共用了13天完成了生产任务。
工厂B的领导为了弥补停电带来的生产延误,决定采用scheduleAtFixedRate方案,即从第8天开始,不间断地加班加点生产零件,直到将因为停电落下的300零件生产完毕,再恢复之前的生产计划每天100零件。结果从第8天开始工厂B用了一天不间断的生产终于补齐了300个零件,从第9天开始,恢复每天生产100个零件(注意:第9天生产的零件是原计划第8天应该生产的)。最终工厂B用了11天就完成了生产任务。
public class Run3 { private static Timer timer = new Timer(); static public class MyTask extends TimerTask{ @Override public void run() { System.out.println("任务执行时间为:"+new Date()); System.out.println("结束时间为:"+new Date()); } } public static void main(String args[]) { try { MyTask mt = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); String dateString = "2018-05-24 19:29:10"; Date dateRef = sdf.parse(dateString); System.out.println("设置的时间:"+dateRef.toLocaleString()+" 当前时间"+new Date().toLocaleString()); timer.schedule(mt, dateRef, 3000); }catch(ParseException e) { e.printStackTrace(); } } }
运行结果如下:
/*
设置的时间:2018-5-24 19:29:10 当前时间2018-5-24 19:49:48
任务执行时间为:Thu May 24 19:49:48 CST 2018
结束时间为:Thu May 24 19:49:48 CST 2018
任务执行时间为:Thu May 24 19:49:51 CST 2018
结束时间为:Thu May 24 19:49:51 CST 2018
任务执行时间为:Thu May 24 19:49:54 CST 2018
结束时间为:Thu May 24 19:49:54 CST 2018
任务执行时间为:Thu May 24 19:49:57 CST 2018
结束时间为:Thu May 24 19:49:57 CST 2018
*/
我们可以看到,虽然执行的时间比原计划延迟了,但是由于schedule不具备追赶执行性,任务依旧是按照我们设置的时间间隔每三秒执行一次。但是如果我们将schedule改成scheduleAtFixedRate之后,情况就不一样了:
/*
任务执行时间为:Thu May 24 19:29:58 CST 2018
结束时间为:Thu May 24 19:29:58 CST 2018
任务执行时间为:Thu May 24 19:29:58 CST 2018
结束时间为:Thu May 24 19:29:58 CST 2018
任务执行时间为:Thu May 24 19:29:58 CST 2018
结束时间为:Thu May 24 19:29:58 CST 2018
任务执行时间为:Thu May 24 19:29:58 CST 2018
结束时间为:Thu May 24 19:29:58 CST 2018
任务执行时间为:Thu May 24 19:30:01 CST 2018
结束时间为:Thu May 24 19:30:01 CST 2018
任务执行时间为:Thu May 24 19:30:04 CST 2018
结束时间为:Thu May 24 19:30:04 CST 2018
任务执行时间为:Thu May 24 19:30:07 CST 2018
结束时间为:Thu May 24 19:30:07 CST 2018
*/
可以看到,红色区域是“追赶执行的”任务,它们会连续执行而不管时间间隔period,直到将落下的任务执行完毕,这就算追赶上了原来计划的任务,从这里开始按照设定的时间间隔执行接下来的任务。这就是scheduleAtFixedRate所具备的追赶执行性。