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

 

时间: 2024-09-25 10:26:39

java.util.Timer分析源码了解原理的相关文章

使用 Java 6 API分析源码

您可曾想过像 Checkstyle 或 FindBugs 这样的工具如何执行静态代码分析吗,或者像 NetBeans 或 Eclipse 这样的集成开发环境(Integrated Development Environments IDE)如何执行快速代码修复或 查找在代码中声明的字段的完全引用吗?在许多情况下,IDE 具有自己的 API 来解析源码并生成标准树 结构,称为 抽象语法树(Abstract Syntax Tree AST) 或"解析树",此树可用于对源码元素的进一步 分析.

能不能获取到java.util.calendar的源码

问题描述 我们都知道这个被封装起来了,sun只提供了接口,但是我不知道是不是开源,网上搜没搜到.我最近在做android的日历,android下的一些获取日期的接口[如getDayAt(introw,intcolumn)]会用到java.util.calendar里面的get(intfield)方法,但是我不太明白其中的参数的意思[如calendar.get(7)].java的docs里是这样解释的:get(intfield)返回给定日历字段的值.在lenient模式下,所有日历字段都被标准化.

java.util.concurrent包源码阅读(一) 源码包的结构

准备花点时间阅读一下java.util.concurrent这个包里面的java代码,大致看了一下,这个包我个人觉得大致可以分为五个部分(如有不对之处,还望指正) 第一部分 Aomic数据类型 这部分都被放在java.util.concurrent.atomic这个包里面,实现了原子化操作的数据类型,包括 Boolean, Integer, Long, 和Referrence这四种类型以及这四种类型的数组类型. 第二部分 锁 这部分都被放在java.util.concurrent.lock这个包

java.util.concurrent包源码阅读(二) java.util.concurrent.atomic包

Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的AomicStampedReferrence,它不是AomicReferrence的子类,而是利用AomicReferrence实现的一个储存引用和Integer组的扩展类 首先,所有原子操作都是依赖于sun.misc.Unsafe这个类,这个类底层是由C++实现的,利用指针来实现数据操作 关于CAS

Java+Mysql学生管理系统源码_java

最近正在学java和数据库,想起以前写的学生管理系统,都是从网上下载,敷衍了事.闲来无事,也就自己写了一个,不过功能实现的不是很多.  开发语言:java: 开发环境:Mysql, java: 开发工具:eclipse 开发此案例,首先得在电脑上有java开发环境和Mysql, java开发环境与Mysql的搭建,就不再叙述了,如果需要,请联系我最下面的联系方式:dingyelf@aliyun.com  此次系统比较简易:数据库中只有一个表:stu;功能:能够对学生增加.删除.修改.开发步骤: 

分析源码到底是为了什么

        今日,好像有不少网友在群里问我分析源码到底为了什么?有些觉得完全没必要,也有的觉得可以.那么这里我就写写分析源码到底是为了什么吧!         首先,你得明确自己的目标,你是为了找一份高薪的工资,或者其他让自己显得更牛逼的动机?还是实实在在的想在使用相关技术的集群出现异常时能够更准确的定位问题.理解问题乃至解决问题.我想,我的目标是:         搞清楚它们的设计.实现细节,更好的运用它们,在不满足业务数据需求的前提下改造它们,乃至将来有一天,我也能参与写出至少现在在我看

Android 自定义相机及分析源码

Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 <intent-filter> <action android:name="android.intent.action.IMAGE_CAPTURE" /> <category android:name="android.intent.category.DEFAULT" />

Java IO 之 OutputStream源码

Writer      :李强强   一.前言 上一篇<Java IO 之 InputStream源码>,说了InputStream.JDK1.0中就有了这传统的IO字节流,也就是 InputStream 和 OutputStream.梳理下两者的核心: InputStream中有几个 read() 方法和 OutputStream中有几个 write() 方法.它们是一一对应的,而核心的是read()和write()方法.它们都没实现,所有本质调用是各自实现类实现的该两个方法. read()

服务器-Java编写ATM项目源码。

问题描述 Java编写ATM项目源码. 急需一个Java编写的银行ATM项目源码:要求:(1)客户端服务器模式 (2)实现基本的开户,存款,取款等功能(少一两个或多一两个都可) (3)支持多用户(越多越好)登录(4)服务器调用数据库存储开户数据等数据或使用文件存储均可,(5)通信方式采用Socket:还有就是纯Java编写,不可掺杂其他语言(没办法,老师要求的) 事关期末考试成绩,求不挂科,谢谢再谢谢了,有源码的请发扣扣972281410,谢谢 解决方案 这里有几个现成的,参考下http://d