【定时任务】JDK java.util.Timer定时器的实现原理
在程序中简单实用Timer的方法,参考学习。
定时任务,也叫定时器,是指在指定的时间执行指定任务的机制,类似于Windows自带的计划任务。JDK中提供了定时器的支持—java.util.Timer,下面我们来系统学习一下它的实现原理。
Timer主要由三个部分组成: 任务TimerTask、任务队列TaskQueue和 任务调试者TimerThread。多个任务单元 TimerTask按照一定的优先级组成了任务队列TaskQueue,任务调度TimerThread按照一定的规则每次取出任务队列中的一个任务进行处理。
TimerTask 任务单元
//*********任务状态*****************//VIRGIN表示Task刚刚被创建static final int VIRGIN = 0;//************几个状态常量******************//SCHEDULED表示Task已经被加入TaskQueue中,等待调度static final int SCHEDULED = 1;//EXECUTED表示Task已经被执行static final int EXECUTED = 2;//CANCELLED表示Task已经被取消//**************两个重要的成员变量******************//nextExecutionTime这个成员变量用到记录该任务下次执行时间, 其格式和System.currentTimeMillis()一致.这个值是作为任务队列中任务排序的依据. 任务调试者执行每个任务前会对这个值作处理,重新计算下一次任务执行时间,并为这个变量赋值.long nextExecutionTime; //period 用来描述任务的执行方式: 0表示不重复执行的任务. 正数表示固定速率执行的任务. 负数表示固定延迟执行的任务. (固定速率: 不考虑该任务上一次执行情况,始终从开始时间算起的每period执行下一次。固定延迟: 考虑该任务一次执行情况,在上一次执行后period执行下一次)。long period = 0;
TaskQueue任务队列
TaskQueue是用来保存TimerTask的队列,是一个数组, 采用平衡二叉堆来实现优先级调度, 并且是一个最小堆, 这个堆中queue[n] 的孩子是queue[2*n] 和 queue[2*n+1]。任务队列的优先级按照TimerTask类的成员变量nextExecutionTime值来排序。在任务队列中, nextExecutionTime最小就是所有任务中最早要被调度来执行的(也就是堆顶的Task), 所以被安排在queue[1] (假设任务队列非空),对于堆中任意一个节点m,和他的任意子孙节点n,一定遵循: m.nextExecutionTime <= n.nextExecutionTime.
下面是TaskQueue的核心代码,也是最小堆的实现代码:
添加任务:
/***首先会判断是否已经满了,如果已经满了, 那么容量扩大至原来2倍, 然后将需要添加的任务放到队列最后. 之后就会调用fixUp 方法来进行队列中任务优先级调整.*/
void add(TimerTask task) { if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2 * queue.length); queue[++size] = task; fixUp(size);
} /*** fixUp方法的作用是尽量将队列中指定位置(k)的任务向队列前面移动, 即提高它的优先级. 因为新加入的方法很有可能比已经在任务队列中的其它任务要更早执行.*/
private void fixUp(int k) { while (k > 1) { int j = k >> 1; // 对于正数,右移位 <==> j = k/2, 所以j的位置就是k的父亲节点 if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; }
}
总结:这个过程可以这个描述: 不断地将k位置上元素和它的父亲进行比较, 如果发现孩子节点比父亲小的时候, 那么将父亲和孩子位置互换. 直到最小的来到队列第一个位置。
移除任务:
/*** 首先直接将当前任务队列中最后一个任务赋给queue[1], 然后将队列中任务数量--, 最后和上面类似, 但是这里是调用fixDown(int k)方法了, 尽量将k位置的任务向队列后面移动.*/
void removeMin() { queue[1] = queue[size]; queue[size--] = null; // Drop extra reference to prevent memory leak fixDown(1);
} /** * 将k位置的元素向堆底方向移动* 1. j = k << 1, .<br> * 2. 将 j 精确定位到较小的儿子.<br> * 3. 然后k与j比较,如果k大于j的话, 那么互换< * 4.继续... */
private void fixDown(int k) { int j; // 如果还没有到队列的最后,并且没有溢出( j > 0 ),在没有出现溢出的情况下, j = k << 1 等价于 j = 2 * k,将j定位到儿子中 while ((j = k << 1) <= size && j > 0) { // 找到k的两个孩子中小的那个. if (j < size && queue[j].nextExecutionTime > queue[j + 1].nextExecutionTime) j++; // 找到这个较小的孩子后,(此时k是父亲,j是较小的儿子),父亲和儿子互换位置,即k和j换位子.这样一直下去就可以将这个较大的queue[1]向下堆底移动了. if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; }
}
TimerThread任务调度
TimerThread就是用来调度TaskQueue中的任务的线程。关于任务调度主要有一个成员变量 newTasksMayBeScheduled和调度方法mainLoop()。
boolean newTasksMayBeScheduled = true; private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired = false; synchronized (queue) { while (queue.isEmpty() && newTasksMayBeScheduled) { queue.wait(); } if (queue.isEmpty()) break; // 直接挑出mainLoop了. long currentTime, executionTime; task = queue.getMin(); // 获取这个任务队列第一个任务 synchronized (task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; } currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; if (taskFired = (executionTime <= currentTime)) { if (task.period == 0) { // period表示不重复,移除出队列 queue.removeMin(); task.state = TimerTask.EXECUTED; } else { //重复执行的设置重新调度 queue.rescheduleMin(task.period < 0 ? currentTime - task.period : executionTime + task.period); } } }//释放锁 if (!taskFired) queue.wait(executionTime - currentTime); } if (taskFired) // Task fired; run it, holding no locks task.run(); } catch (InterruptedException e) { } }// while(true) }
newTasksMayBeScheduled变量用来表示是否需要继续等待新任务了。默认情况这个变量是true , 并且这个变量一直是true的。只有两种情况的时候会变成 false :
1.当调用Timer的cancel方法
2.没有引用指向Timer对象了.
任务调度mainLoop()方法中的一个while可以理解为一次任务调度:
STEP 1 : 判断任务队列中是否还有任务, 如果任务队列为空了, 但是newTasksMayBeScheduled变量还是true, 表明 需要继续等待新任务, 所以一直等待。
STEP 2 : 等待唤醒后, 再次判断队列中是否有任务. 如果还是没有任务,那么直接结束定时器工作了.因为queue只在两个地方被调用: addTask和cancel 1.向任务队列中增加任务会唤醒 2.timer.cancel()的时候也会唤醒. 那么这里如果还是empty,那么就是cancel的唤醒了,所以可以结束timer工作了。
STEP 3 : 从任务队列中取出第一个任务,即nextExecutionTime最小的那个任务。
STEP 4: 判断这个任务是否已经被取消. 如果已经被取消了,那么就直接从任务队列中移除这个任务(removeMin() ),然后直接进入下一个任务调度周期。
STEP 5 : 判断是否到了或者已经超过了这个任务应该执行的时间了。如果到了 , 不会立即执行它,而是会在这次循环的最后来执行它。
这里做的事情可以看作是为下一个调度周期进行准备:包括:
1. 判断是否是重复(repeating)任务,如果 task.period == 0, 那么就不是重复任务,所以可以直接将这个任务从任务队列中移除了(removeMin() ),因为没有必要留到下一个调度周期中去了.2. 如果是需要重复执行的任务, 那么就要重新设置这个任务的nextExecutionTime,即调用方法queue.rescheduleMin(long) ,这个方法中会调用fixDown(1) 负责重新调整任务队列的优先级顺序.
如果还没有到执行时间 , 一直等到 queue.wait(executionTime - currentTime),并且等待完毕后,似乎可以开始运行了, 但是这里设计成不立即运行,而是直接进入下一个任务调度周期.(因为taskFired =false,所以不会在这次进行执行的.)
STEP6: 开始调用任务的run方法运行任务。
还有一点需要注意:
在step2中我们学习到,TimerThread的调度核心是起一个while循环,不断检查是否有task需要执行,其中两次调用了queue.wait()方法。1.向任务队列中增加任务会唤醒 2.timer.cancel()的时候也会唤醒,两种情况下queue.notify()方法会被调用。
但是是否上面两种情况调用notify就已经足够了?当queue为空,并且没人调用add或cancel方法时,TimerThread永远都不会stop。不用担心,对于这个地方的处理JDK加上了一种比较保险的方法:
/*** This object causes the timer's task execution thread to exit* gracefully when there are no live references to the Timer object and no* tasks in the timer queue. It is used in preference to a finalizer on* Timer as such a finalizer would be susceptible to a subclass's* finalizer forgetting to call it.*/
private final Object threadReaper = new Object() {protected void finalize() throws Throwable {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.notify(); // In case queue is empty.}}
};
用到了Object对象的finalize方法,大家都知道finalize方法是对象被GC的时候调用的。上述做法的思路是:当一个Timer已经没有任何对象引用时,自然不会有新的Task加入到队列中,Timer对象自然也就会被垃圾回收,此时TimerThread也就应该stop了,所以在垃圾回收的时候还应该把newTasksMayBeScheduled设置为false,并且唤起正在wait的TimerThread线程。所以说,如果你创建的Timer不再需要了,最好是调用cancel接口手动取消,否则的话TimerThread就需要等到垃圾回收的时候才会stop。
【定时任务】JDK java.util.Timer定时器的实现原理相关推荐
- Java 中Timer定时器设置订单提交后24小时未付款订单状态为已关闭。
1. 简单的Timer定时器方法 public class CommTimer {/*** 设置指定24小时后执行*/public static void orderClose() {final Ti ...
- 使用java.util.Timer来周期性的执行制定的任务
使用java.util.Timer来周期性的执行制定的任务 1 public class HandlerTest extends Activity { 2 int[] images = new int ...
- java没有timer类_Java中的Java.util.Timer类 - Break易站
scheduleAtFixedRate(TimerTask task, long delay, long period): java.util.Timer.scheduleAtFixedRate(Ti ...
- java的Timer定时器
import java.util.Calendar; import java.util.Date; import java.util.Timer; import java.util.TimerTask ...
- java timer demo_java中任务调度java.util.Timer,ScheduledExecutor,Quartz的机制说明和demo代码实例分享...
目前的 Web 应用,多数应用都具备任务调度的功能.这里就简单的介绍任务调度的Java 实现方法,主要包括 Timer,Scheduler, Quartz 以及 JCron Tab,目的在于给需要开发 ...
- java.util.timer 定时任务_java.util系列源码解读之Timer定时器
Timer是jdk1.3中自带的定时任务框架系统.一个调度定时任务的工具线程类.可以执行一个只调度一次的任务也可以重复调度一个一定间隔时间的任务. 一个Timer实例就是一个调度任务调度线程.当任务队 ...
- java timer定时执行一次_用java.util.Timer定时执行任务
classWorker extends TimerTask{ publicvoidrun(){ System.out.println("我在工作啦!"); }} Tim ...
- java Timer定时器管理类
1.java timer类,定时器类.启动执行定时任务方法是timer.schedule(new RemindTask(), seconds*1000);俩参数分别是TimerTask子类,具体执行定 ...
- 定时任务:Java中Timer和TimerTask的使用
2019独角兽企业重金招聘Python工程师标准>>> java.util.Timer定时器,实际上是个线程,定时调度所拥有的TimerTasks. 一个TimerTask实际上就 ...
最新文章
- Openresty最佳案例 | 第5篇:http和C_json模块
- 网站访问慢解决思路详细图解
- 专栏-美国人口和都市区
- WordPress 数据库结构及表字段作用解析
- ctr 平滑_CTR预估中的贝叶斯平滑方法及其代码实现
- python 股票数据_从互联网获取股票数据(历史数据,Python + MySQL)
- SQL 2005 新功能
- 特斯拉:芯片短缺至移动充电连接器涨价
- boost linux 测试程序,Linux平台下安装 boost 库
- [刀塔自走棋] 一些数据
- 撞线百亿后,良品铺子峥嵘毕现?
- MySQL8.0.26的时候解压libs文件出现错误:依赖检测失败:mariadb-libs 被 mysql-community-libs-8.0.26-1.el7.x86_64 取代
- latex显示错误:Text line contains an invalid character. l.1
- mysql里的char怎么添加数据类型_MySQL CHAR 数据类型
- RF:Robot命令行工具帮助文件中文译版(个人翻译)
- [iWencai]问财-热门股票排名
- win10如何显示我的电脑在桌面
- encode() decode() 编码解码函数
- Mock工具之Moco使用教程
- 英语知识点整理day04