网站首页 > java教程 正文
Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用。从Java5开始在并发包中引入了另一个定时器ScheduledThreadPoolExecutor,它对Timer做了很多改进并提供了更多的工具,可以认为是对Timer的取代。
那为什么还要介绍Timer工具类呢?通过了解Timer的功能和它背后的原理,有助于我们更好的对比了解ScheduledThreadPoolExecutor,同时ScheduledThreadPoolExecutor的一些改进思想在我们平时的编码工作中也可以借鉴。
主要成员变量
Timer中用到的主要是两个成员变量:
- TaskQueue:一个按照时间优先排序的队列,这里的时间是每个定时任务下一次执行的毫秒数(相对于1970年1月1日而言)
- TimerThread:对TaskQueue里面的定时任务进行编排和触发执行,它是一个内部无限循环的线程。
//根据时间进行优先排序的队列
private final TaskQueue queue = new TaskQueue();
//消费线程,对queue中的定时任务进行编排和执行
private final TimerThread thread = new TimerThread(queue);
//构造函数
public Timer(String name) {
thread.setName(name);
thread.start();
}
定时功能
Timer提供了三种定时模式:
- 一次性任务
- 按照固定的延迟执行(fixed delay)
- 按照固定的周期执行(fixed rate)
第一种比较好理解,即任务只执行一次;针对第一种,Timer提供了以下两个方法:
//在当前时间往后delay个毫秒开始执行
public void schedule(TimerTask task, long delay) {...}
//在指定的time时间点执行
public void schedule(TimerTask task, Date time) {...}
第二种Fixed Delay模式也提供了以下两个方法
//从当前时间开始delay个毫秒数开始定期执行,周期是period个毫秒数
public void schedule(TimerTask task, long delay, long period) {...}
////从指定的firstTime开始定期执行,往后每次执行的周期是period个毫秒数
public void schedule(TimerTask task, Date firstTime, long period){...}
它的工作方式是:
第一次执行的时间将按照指定的时间点执行(如果此时TimerThread不在执行其他任务),如有其他任务在执行,那就需要等到其他任务执行完成才能执行。
从第二次开始,每次任务的执行时间是上一次任务开始执行的时间加上指定的period毫秒数。
如何理解呢,我们还是看代码
public static void main(String[] args) {
TimerTask task1 = new DemoTimerTask("Task1");
TimerTask task2 = new DemoTimerTask("Task2");
Timer timer = new Timer();
timer.schedule(task1, 1000, 5000);
timer.schedule(task2, 1000, 5000);
}
static class DemoTimerTask extends TimerTask {
private String taskName;
private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
public DemoTimerTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(df.format(new Date()) + taskName + " is working.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(df.format(new Date()) + taskName + " finished work.");
}
}
task1和task2是几乎同时执行的两个任务,而且执行时长都是2秒钟,如果此时我们把第六行注掉不执行,我们将得到如下结果(和第三种Fixed Rate模式结果相同):
13:42:58---Task1 is working.
13:43:00---Task1 finished work.
13:43:03---Task1 is working.
13:43:05---Task1 finished work.
13:43:08---Task1 is working.
13:43:10---Task1 finished work.
如果打开第六行,我们再看下两个任务的执行情况。我们是期望两个任务能够同时执行,但是Task2是在Task1执行完成后才开始执行(原因是TimerThread是单线程的,每个定时任务的执行也在该线程内完成,当多个任务同时需要执行时,只能是阻塞了),从而导致Task2第二次执行的时间是它上一次执行的时间(13:43:57)加上5秒钟(13:44:02)。
13:43:55---Task1 is working.
13:43:57---Task1 finished work.
13:43:57---Task2 is working.
13:43:59---Task2 finished work.
13:44:00---Task1 is working.
13:44:02---Task1 finished work.
13:44:02---Task2 is working.
13:44:04---Task2 finished work.
那如果此时还有个Task3也是同样的时间点和间隔执行会怎么样呢?
结论是:也将依次排队,执行的时间依赖两个因素:
1.上次执行的时间
2.期望执行的时间点上有没有其他任务在执行,有则只能排队了
我们接下来看下第三种Fixed Rate模式,我们将上面的代码稍作修改:
public static void main(String[] args) {
TimerTask task1 = new DemoTimerTask("Task1");
TimerTask task2 = new DemoTimerTask("Task2");
Timer timer = new Timer();
timer.scheduleAtFixedRate(task1, 1000, 5000);
timer.scheduleAtFixedRate(task2, 1000, 5000);
}
static class DemoTimerTask extends TimerTask {
private String taskName;
private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
public DemoTimerTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(df.format(new Date()) + taskName + " is working.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(df.format(new Date()) + taskName + " finished work.");
}
}
Task1和Task2还是在相同的时间点,按照相同的周期定时执行任务,我们期望Task1能够每5秒定时执行任务,期望的时间点是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,实际上它能够交替着定期执行,原因是Task2也会定期执行,并且对TaskQueue的锁他们是交替着拿的(这个在下面分析TimerThread源码的时候会讲到)
14:21:47---Task1 is working.
14:21:49---Task1 finished work.
14:21:49---Task2 is working.
14:21:51---Task2 finished work.
14:21:52---Task2 is working.
14:21:54---Task2 finished work.
14:21:54---Task1 is working.
14:21:56---Task1 finished work.
14:21:57---Task1 is working.
14:21:59---Task1 finished work.
14:21:59---Task2 is working.
14:22:01---Task2 finished work.
TimerThread
上面我们主要讲了Timer的一些主要源码及定时模式,下面我们来分析下支撑Timer的定时任务线程TimerThread。
TimerThread大概流程图如下:
源码解释如下:
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 如果queue里面没有要执行的任务,则挂起TimerThread线程
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
// 如果TimerThread被激活,queue里面还是没有任务,则介绍该线程的无限循环,不再接受新任务
if (queue.isEmpty())
break;
long currentTime, executionTime;
// 获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// taskFired表示是否需要立刻执行线程,当task的下次执行时间到达当前时间点时为true
if (taskFired = (executionTime<=currentTime)) {
//task.period==0表示这个任务只需要执行一次,这里就从queue里面删掉了
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
//针对task.period不等于0的任务,则计算它的下次执行时间点
//task.period<0表示是fixed delay模式的任务
//task.period>0表示是fixed rate模式的任务
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// 如果任务的下次执行时间还没有到达,则挂起TimerThread线程executionTime - currentTime毫秒数,到达执行时间点再自动激活
if (!taskFired)
queue.wait(executionTime - currentTime);
}
// 如果任务的下次执行时间到了,则执行任务
// 注意:这里任务执行没有另起线程,还是在TimerThread线程执行的,所以当有任务在同时执行时会出现阻塞
if (taskFired)
// 这里没有try catch异常,当TimerTask抛出异常会导致整个TimerThread跳出循环,从而导致Timer失效
task.run();
} catch(InterruptedException e) {
}
}
}
结论
通过上面的分析,我们可以得出以下结论:
- Timer支持三种模式的定时任务(一次性任务,Fixed Delay模式,Fixed Rate模式)
- Timer中的TimerThread是单线程模式,因此导致所有定时任务不能同时执行,可能会出现延迟
- TimerThread中并没有处理好任务的异常,因此每个TimerTask的实现必须自己try catch防止异常抛出,导致Timer整体失效
Demo代码位置
src/main/java/net/weichitech/util/TimerFixedDelayDemo.java · 小西学编程/java-learning - Gitee.com
src/main/java/net/weichitech/util/TimerFixedRateDemo.java · 小西学编程/java-learning - Gitee.com
猜你喜欢
- 2024-09-09 Java 定时器、加密、File类(java定时器时间格式)
- 2024-09-09 100个Java工具类之67:定时执行Timer
- 2024-09-09 JAVA定时器的嵌套实现(java的定时器能不能提供实时保证 可能提前也可能推迟)
- 2024-09-09 Java中分布式定时器实现和原理介绍
- 2024-09-09 Java定时任务——SpringTask(java定时任务的实现方式)
- 2024-09-09 全面了解Java Timer定时器类(java定时器怎么用)
- 2024-09-09 Java多线程19:定时器Timer(java多线程实现方式)
- 2024-09-09 Java中的定时器:java.util.Timer(java中的定时器有哪些)
- 2024-09-09 Java 中如何实现定时任务(java定时任务底层原理)
- 2024-09-09 Java定时任务的五种创建方式,你都会么?
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)