`

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.", 那么在不断比较过程中, 如果发现孩子节点比父亲小的时候, 那么将父亲和孩子位置互换. 直到来到队列第一个位置.

fixUp(int newTask)过程

[移除任务]

	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

 

分享到:
评论

相关推荐

    java.util.ConcurrentModificationException 异常问题详解1

    Java.util.ConcurrentModificationException 异常问题详解 ConcurrentModificationException 异常是 Java 中一个常见的异常,它发生在 Iterator 遍历集合时,集合同时被修改引起的异常。在 Java 中,集合类如 ...

    java timer定时器详解(附详细代码)

    Java Timer定时器主要是通过java.util.Timer和java.util.TimerTask两个类来实现的。 Java.util.Timer类 java.util.Timer类是Java Timer定时器的核心类,它可以执行任务并且可以指定任务的执行频率。Timer类有两种...

    java定时关机源码

    总结一下,Java定时关机源码主要涉及了Java的定时任务处理,可以使用`java.util.Timer`或`java.util.concurrent.ScheduledExecutorService`来实现。通过这两个工具,开发者可以设置在特定时间执行关闭系统的任务,...

    Java并发Timer源码分析

    Java并发Timer源码分析 Java并发编程是Java编程中一个非常重要的方面,java.util.Timer是Java中的一个基本组件,用于实现延时和周期性任务的执行。但是,Timer存在一些缺陷,如创建唯一的线程来执行所有Timer任务,...

    javaalert_JAVA源码_

    在Java中,创建一个定时提醒任务通常涉及到使用`java.util.Timer`类或`java.time`包中的定时器功能。让我们深入探讨这个主题。 Java中的定时任务通常用于执行周期性的操作,比如定时发送通知、执行数据备份或在特定...

    java定时执行方法&节拍器

    总结一下,Java中实现定时任务主要依靠`java.util.Timer`、`java.util.concurrent.ScheduledExecutorService`等原生API,以及一些第三方库如Quartz。在实际开发中,选择合适的定时任务解决方案需要考虑项目的复杂性...

    java 实现调度器

    这通常是通过Java中的`java.util.Timer`类或者`java.util.concurrent.ScheduledExecutorService`来实现的。这两个工具提供了不同的功能和使用场景,让我们一一进行深入探讨。 首先,我们来看`java.util.Timer`类。...

    Java定时执行某个任务

    首先,我们来了解`java.util.Timer`类。这个类允许开发者创建一个定时器,它可以安排在特定时间执行的任务。以下是一个简单的使用示例: ```java import java.util.Timer; import java.util.TimerTask; public ...

    倒计时系统.java源码

    - **定时器**:Java提供了`java.util.Timer`和`java.util.TimerTask`类来安排任务在将来某个时间点或周期性地执行,这在倒计时系统中至关重要。 3. **事件驱动编程**: - **监听器**:在GUI应用中,可能需要监听...

    JAVA定时任务

    在实际开发中,源码分析对于理解定时任务的工作原理至关重要。例如,通过阅读`ScheduledThreadPoolExecutor`的源码,我们可以了解其调度策略和线程池的管理方式。同时,利用IDE(如IntelliJ IDEA或Eclipse)的调试...

    JAVA定时器

    在Java中,主要有两种定时器类:`java.util.Timer` 和 `java.util.concurrent.ScheduledExecutorService`。这篇博文可能是分析了这两种定时器的使用方法和源码解析。 `java.util.Timer` 是Java早期提供的定时器,它...

    Timer简单程序代码

    1. **Java中的Timer**: Java的`java.util.Timer`和`java.util.TimerTask`类组合使用可以创建定时器。`Timer`用于调度任务,`TimerTask`是可重用的轻量级任务类。例如,创建一个每秒打印一行的Timer任务,可以这样...

    JAVA语言时钟源码

    在Java编程语言中,源码通常用于描述程序员编写的代码,这些代码在编译后会被转换...通过分析这些源码,开发者可以学习到Java编程中的时间处理、GUI编程技巧以及面向对象设计原则,这对于提升Java编程能力非常有帮助。

    timer_timer_源码

    例如,Java中的`java.util.Timer`类和`java.util.TimerTask`接口,它们可以用来定期执行任务。在Python中,我们可以使用`threading.Timer`来实现定时功能。C++则通常依赖于操作系统提供的定时器API,如POSIX的`alarm...

    Notforget-java.rar_java 提醒_java提醒_提醒

    在Java中,我们可以使用定时器(java.util.Timer)类和定时任务(java.util.TimerTask)来安排在未来某个时间执行特定任务,比如显示一个提醒对话框。定时器可以用来一次性执行任务,也可以按照预设的时间间隔重复...

    Spring 使用Timer quartz区别

    在处理定时任务时,Spring提供了两种常见的解决方案:`java.util.Timer` 和 Quartz。这两个工具各有特点,适用于不同的场景。下面我们将深入探讨它们的区别。 首先,`java.util.Timer` 是Java标准库中的一个简单...

    基于java语言开发的提醒簿程序源码

    6. **事件驱动编程**:如果程序具有定时提醒功能,那么它可能使用了Java的定时器(java.util.Timer)或ScheduledExecutorService来定期执行任务。事件驱动编程的概念也会体现在用户交互上,比如按钮点击触发提醒的...

    java5定时器java Timer

    Java5中的`java.util.Timer`类是一个非常实用的工具,用于调度周期性的任务执行。它在多线程环境中提供了一种高效且灵活的方式来安排任务在未来某个时间点或定期执行。这个类是Java早期版本中对定时任务管理的一个...

    规定时间执行规定任务

    首先,Java标准库提供了一个名为`java.util.Timer`和`java.util.TimerTask`的类,它们可以用来创建简单的定时任务。`Timer`类用于调度任务,而`TimerTask`是实际的任务对象。下面是一个简单的例子: ```java import...

    基于Java的源码-jalarm(个人提醒工具 jAlarm).zip

    Java的`java.util.Timer`类和`TimerTask`接口可以用来创建定时任务,定期执行特定操作,如触发提醒。开发者可能通过这两个类实现提醒的定时触发,并结合`java.awt.event.ActionEvent`和`ActionListener`处理用户交互...

Global site tag (gtag.js) - Google Analytics