一:简介
在JDK类库中Timer主要负责计划任务的功能,也就是在指定时间执行某一任务,执行时候会在主线程之外起一个单独的线程执行指定的任务。该类主要是设置任务计划,但封装的类是TimerTask类。
TimerTask是一个实现了Runnable接口的抽象类,代表一个可被执行的任务,执行任务的代码要放在其子类中(TimerTask是抽象类)。
二:Timer整体类图
TimerTask类:主要为定时任务的具体内容。
Timer类中含有3个类:Timer、TimerThread、TaskQueue。
Timer类主要是设置定时任务,配置用户期望的任务执行时间、执行次数、执行内容
TimerThread类为Thread的扩展类,会一直从TaskQueue中获取下标为1的TimerTask进行执行。并根据该TimerTask是否需要重复执行来决定是否放回到TaskQueue中。
TaskQueue中存放一些列将要执行的TimerTask,以数组的形式存放,下标约小(注:下标为0不处理,即使用的最小下标为1),则表明优先级越高。
public class Timer {
//默认给线程命名:"Timer-" + serialNumber()
public Timer() {
this("Timer-" + serialNumber());
}
//指定Timer的名字
public Timer(String name) {
thread.setName(name);
thread.start();
}
//isDaemon:是否为守护线程
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
//给定线程名字,守护线程与否
public Timer(String name, boolean isDaemon) {
thread.setName(name);
//setDaemon是指定线程是否为守护线程,true是守护线程,当主线程退出, 守护线程退出
thread.setDaemon(isDaemon);
thread.start();
}
}
//TimerThread类 继承Thread
class TimerThread extends Thread{
private void mainLoop() {
......
//每次执行下标为1的TimerTask
task = queue.getMin();
......
}
}
//TaskQueue 类
class TaskQueue {
//TaskQueue 中存储TimerTask
private TimerTask[] queue = new TimerTask[128];
//返回数组中下标1的TimerTask
TimerTask getMin() {
return queue[1];
}
}
三:Timer常用方法
1.Timer 类void schedule(TimerTask task, Date time)方法
1.task是定时任务,time是task任务执行时间.如果time时间早于当前则立即执行,否则在time时间执行。
2.task只执行一次,且执行完Timer线程任务不结束,因为Timer不是守护线程,Timer timer = new Timer(true);
是指定Timer 为守护线程,当task执行结束后,Timer 线程会立即结束。
3.Timer 可以执行多个定时任务,TimerTask 将会按照队列的方式一个一个被执行,如果前面的任务耗时长,后面的任务执行时间将会相应的延后。
public class TimerDemo {
//指定Timer为守护线程,run方法结束Timer线程会结束。
private static Timer timer = new Timer(true);
//TimerTask 具体的定时任务
static public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("已运行,时间为:" + new Date());
}
}
public static void main(String[] args) {
//创建定时任务
MyTask myTask = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//早于当前时间,定时任务会立即执行
String dateString = "2020-06-01 09:56:00";
try {
Date taskDate = sdf.parse(dateString);
//启动定时任务
timer.schedule(myTask,taskDate);
} catch (Exception e) {
e.printStackTrace();
}
}
}
已运行,时间为:Wed Jul 01 14:48:06 CST 2020
2.Timer 类void schedule(TimerTask task, Date firstTime, long period)方法
TimerTask任务在指定 firstTime任务之后,周期性每隔period时间,无限期循环的执行某一任务。
3.TimerTask 类boolean cancel()方法
TimerTask 类boolean cancel()方法,是将自身任务从任务队列中移除。
public class TimerDemo {
private static Timer timer = new Timer();
static public class MyTaskA extends TimerTask {
@Override
public void run() {
//只输出一次,MyTaskA 任务将会移除
System.out.println("MyTaskA已运行,时间为:" + new Date());
//执行TimerTask的cancel方法,将自身任务从队列中移除
this.cancel();
}
}
static public class MyTaskB extends TimerTask {
@Override
public void run() {
//移除MyTaskA任务,MyTaskB 任务不受影响
System.out.println("MyTaskB已运行,时间为:" + new Date());
}
}
public static void main(String[] args) {
MyTaskA myTaskA = new MyTaskA();
MyTaskB myTaskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2020-07-01 15:19:00";
try {
Date taskDate = sdf.parse(dateString);
timer.schedule(myTaskA,taskDate,2000);
timer.schedule(myTaskB,taskDate,2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyTaskA已运行,时间为:Wed Jul 01 15:24:47 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:47 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:49 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:51 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:53 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:55 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:57 CST 2020
MyTaskB已运行,时间为:Wed Jul 01 15:24:59 CST 2020
4.Timer 类void cancel()方法
Timer 类void cancel()方法与TimerTask 类boolean cancel()方法将自身任务从任务队列中移除不同,它是将所有的任务都从队列中移除。
public class TimerDemo {
private static Timer timer = new Timer();
static public class MyTaskA extends TimerTask {
@Override
public void run() {
System.out.println("MyTaskA已运行,时间为:" + new Date());
//执行Timer类的cancel方法,将Timer中所有任务从队列中移除
timer.cancel();
}
}
static public class MyTaskB extends TimerTask {
@Override
public void run() {
//MyTaskB也会被移除,因为MyTaskA与MyTaskB是队列执行,先执行MyTaskA再执行MyTaskB
//MyTaskA中走了timer.cancel(),所以MyTaskB不会执行
System.out.println("MyTaskB已运行,时间为:" + new Date());
}
}
public static void main(String[] args) {
MyTaskA myTaskA = new MyTaskA();
MyTaskB myTaskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2020-07-01 15:19:00";
try {
Date taskDate = sdf.parse(dateString);
timer.schedule(myTaskA,taskDate,2000);
timer.schedule(myTaskB,taskDate,2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyTaskA已运行,时间为:Wed Jul 01 16:20:48 CST 2020
源码:
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
执行cancel需要抢占queue锁,如果抢不到,则TimerTask 任务会继续执行。
5.Timer 类 void schedule(TimerTask task, long delay)方法
该方法以schedule(TimerTask task, long delay)方法的执行时间为基准,在此时间上延迟指定的毫秒数delay后执行一次TimerTask 任务。
6.Timer 类 void schedule(TimerTask task, long delay, long period)方法
该方法以schedule(TimerTask task, long delay, long period)方法的执行时间为基准,在此时间上延迟指定的毫秒数delay后,执行TimerTask 任务,之后间隔period毫秒无限循环执行TimerTask 任务。
7.Timer 类void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)方法
TimerTask任务在指定 firstTime任务之后,周期性每隔period时间,无限期循环的执行某一任务。
四:重点:scheduleAtFixedRate(TimerTask task, Date firstTime, long period)与 schedule(TimerTask task, Date firstTime, long period)方法区别
1.相同点:两个方法都是按照顺序执行,无线程安全问题
2.任务不延时相同点:如果任务执行时间小于任务时间间隔,则第二次任务的开始时间是任务开始时间+period
3.任务延时相同点:如果任务执行时间大于任务时间间隔,导致第二次任务开始时候,上一个任务还未结束,则第二次任务的开始时间是在第一次任务结束时候立即执行。
scheduleAtFixedRate方法与schedule方法的追赶性问题
scheduleAtFixedRate方法具有追赶性!!!!如果计划执行时间早于当前时间,执行scheduleAtFixedRate方法后立即执行定时任务,根据任务间隔时间与当前时间减去计划执行时间,补上之前少执行的任务。
schedule方法不具有追赶性。!!!!
public class TimerDemo {
private static Timer timer = new Timer();
static public class MyTaskA extends TimerTask {
@Override
public void run() {
System.out.println("MyTaskA已运行,时间为:" + new Date());
try {
//任务执行时间是1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyTaskA已结束,时间为:" + new Date());
}
}
public static void main(String[] args) {
MyTaskA myTaskA = new MyTaskA();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2020-07-02 14:31:00";
try {
Date taskDate = sdf.parse(dateString);
//3s执行一次任务
timer.scheduleAtFixedRate(myTaskA,taskDate,3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyTaskA已运行,时间为:Thu Jul 02 14:34:38 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:39 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:39 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:40 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:40 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:41 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:41 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:42 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:42 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:43 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:43 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:44 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:44 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:45 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:45 CST 2020
MyTaskA已结束,时间为:Thu Jul 02 14:34:46 CST 2020
MyTaskA已运行,时间为:Thu Jul 02 14:34:46 CST 2020
由上述执行结果可知,在Thu Jul 02 14:34:38 CST 2020启动定时任务后,没有间隔3s再执行下一次任务,而是任务结束就立即执行,这是因为任务计划执行时间早于当前时间,需要将这期间没有执行的任务补上。
五: 总结
java中可以使用定时任务的功能,针对不同的定时任务可以根据不同的API进行处理,从本质来说改技术仍然属于多线程,基于多线程实现,不管用再多的框架实现定时器功能,请不要忘了这事java基础,框架实现的基础。