java.util.Timer分析源码了解原理
Timer中最主要由三个部分组成: 任务 TimerTask 、 任务队列: TaskQueue queue 和 任务调试者:TimerThread thread
他们之间的关系可以通过下面图示:

在这个图中,可以清楚地看到这Timer本身及其和这三个部分的关系:
1. Timer可以看作是面向开发人员的一个"接口"
2. 所有向Timer添加的任务都会被放入一个TaskQueue类型的任务队列中去.(如何安排任务优先级顺序下文会讲)
3. 任务调度由TimerThread负责
任务单元 TimerTask
首先看一下任务单元实体类: TimerTask.
在这个类中, 要关注的是任务状态和几个状态常量:
/** 标识任务的状态 */
int state = VIRGIN;
/** 任务的状态的常量 */
static final int VIRGIN = 0;
static final int SCHEDULED = 1;
static final int EXECUTED = 2;
static final int CANCELLED = 3;
以及一个比较重要的两个成员变量:
long nextExecutionTime;
long period = 0;
nextExecutionTime 这个成员变量用到记录该任务下次执行时间, 其格式和System.currentTimeMillis()一致.
这个值是作为任务队列中任务排序的依据. 任务调试者执行每个任务前会对这个值作处理,重新计算下一次任务执行时间,并为这个变量赋值.
period 用来描述任务的执行方式: 0表示不重复执行的任务. 正数表示固定速率执行的任务. 负数表示固定延迟执行的任务.
(固定速率: 不考虑该任务上一次执行情况,始终从开始时间算起的每period执行下一次. 固定延迟: 考虑该任务一次执行情况,在上一次执行后period执行下一次).
任务队列 TaskQueue
事实上任务队列是一个数组, 采用平衡二叉堆来实现他的优先级调度, 并且是一个小顶堆. 需要注意的是, 这个堆中queue[n] 的孩子是queue[2*n] 和 queue[2*n+1].
任务队列的优先级按照TimerTask类的成员变量nextExecutionTime值来排序(注意, 这里的任务指的是那些交由定时器来执行的, 继承TimerTask的对象). 在任务队列中, nextExecutionTime最小就是所有任务中最早要被调度来执行的, 所以被安排在queue[1] (假设任务队列非空). 对于堆中任意一个节点n, 和他的任意子孙节点d,一定遵循: n.nextExecutionTime <= d.nextExecutionTime.
[添加任务]
void add(TimerTask task) {
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2 * queue.length);
queue[++size] = task;
fixUp(size);
}
首先会判断是否已经满了,(任务队列的初始容量是128 ),如果已经满了, 那么容量扩大至原来2倍, 然后将需要添加的任务放到队列最后.
之后就会调用fixUp 方法来进行队列中任务优先级调整. 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位置上元素和它的父亲进行比较, 上文也提到过了. 由于必须满足 "对于堆中任意一个节点n, 和他的任意子孙节点d,一定遵循: n.nextExecutionTime <= d.nextExecutionTime.", 那么在不断比较过程中, 如果发现孩子节点比父亲小的时候, 那么将父亲和孩子位置互换. 直到来到队列第一个位置.

[移除任务]
void removeMin() {
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
}
从任务队列中移除一个任务的过程, 首先直接将当前任务队列中最后一个任务赋给queue[1], 然后将队列中任务数量--, 最后和上面类似, 但是这里是调用fixDown(int k)方法了, 尽量将k位置的任务向队列后面移动.
/**
* -将k位置的元素向堆底方向移动.<br>
* 1. j = k << 1, 将j定位到儿子中.<br>
* 2. 将 j 精确定位到较小的儿子.<br>
* 3. 然后k与j比较,如果k大于j的话, 那么互换<br>
* 4.继续...
*/
private void fixDown(int k) {
int j;
// 如果还没有到队列的最后,并且没有溢出( j > 0 )
// 在没有出现溢出的情况下, j = k << 1 等价于 j = 2 * k ;
while ((j = k << 1) <= size && j > 0) {
// 找到k的两个孩子中小的那个.
if (j < size && queue[j].nextExecutionTime > queue[j + 1].nextExecutionTime)
j++; // j indexes smallest kid
// 找到这个较小的孩子后,(此时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
关于任务调度主要要讲下一个成员变量 newTasksMayBeScheduled 和 调度方法 mainLoop().
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) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
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,所以不会在这次进行执行的.)
STEP: 6 开始调用任务的run方法运行任务.
http://www.java1995.com/blog/item/516
http://japi.iteye.com/blog/1022656
http://blog.csdn.net/liziyun537/article/details/6710834
http://www.java1995.com/blog/item/516
http://japi.iteye.com/blog/1022656
http://www.cnblogs.com/visec479/p/4096880.html
java.util.Timer分析源码了解原理的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
- 《java.util.concurrent 包源码阅读》04 ConcurrentMap
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
- 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
- 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
- java.util.concurrent.DelayQueue 源码学习
jdk1.8 DelayQueue,带有延迟元素的线程安全队列,当非阻塞从队列中获取元素时,返回最早达到延迟时间的元素,或空(没有元素达到延迟时间).DelayQueue的泛型参数需要实现Delaye ...
- java.util.concurrent ThreadPoolExecutor源码分析
实现的接口:Executor, ExecutorService 子类:ScheduledThreadPoolExecutor 这类为java线程池的管理和创建,其中封装好的线程池模型在Executor ...
- 《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验
JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多线程的方式来执行一个任务. ...
随机推荐
- struts2笔记10-值栈
1.问题 提交页面: <h4>注册产品</h4> <form action="save.do" method="post"> ...
- web2py官方文档翻译01
第一章:介绍 介绍 web2py(web2py)是一个免费的开源web框架的敏捷开发安全的数据库驱动的web应用程序,这是用Python编写的Python(Python)和可编程.web2py是一个完 ...
- cat监控平台环境搭建
项目地址:https://github.com/dianping/cat 编译步骤: 这个项目比较另类,把编译需要的jar包,单独放在git分支mvn-repo里了,而且官方文档里给了一个错误的命令提 ...
- CSS预处理器实践之Sass、Less比较
什么是CSS预处理器? CSS可以让你做很多事情,但它毕竟是给浏览器认的东西,对开发者来说,Css缺乏很多特性,例如变量.常量以及一些编程语法,代码难易组织和维护.这时Css预处理器就应运而生了.Cs ...
- hdu 4740
题目链接 老虎左拐,老鼠右拐,碰到不能走的拐一次,如果还不能走就停下,自己走过的不能走,求相遇的坐标或-1 一个停下之后,另一个还可以走 #include <cstdio> #includ ...
- python 如何判断对象是否为类(class)
if type(att).__name__ == 'classobj': pass else: pass
- Win7下unetbootin-windows-585工具制作Ubuntu12.04 U盘启动盘
1.下载unetbootin-windows-585工具,网址如下: unetbootin-windows-585 2.unetbootin-windows-585制作U盘启动盘 准备好1个4G的U盘 ...
- Minimum Transport Cost(floyd+二维数组记录路径)
Minimum Transport Cost Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/O ...
- Android消息推送之GCM方式(二)
<声明> 转载请保留本来源地址: http://blog.csdn.net/wzg_1987/article/details/9148023 上一节讲了GCM方式实现前的一些必要准备工作, ...
- Codeforces Round #256 (Div. 2/C)/Codeforces448C_Painting Fence(分治)
解题报告 给篱笆上色,要求步骤最少,篱笆怎么上色应该懂吧,.,刷子能够在横着和竖着刷,不能跳着刷,,, 假设是竖着刷,应当是篱笆的条数,横着刷的话.就是刷完最短木板的长度,再接着考虑没有刷的木板,,. ...