java java.util.timer_细说java.util.Timer

Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示。任务的执行方式有两种:

按固定速率执行:即scheduleAtFixedRate的两个重载方法

按固定延迟执行:即schedule的4个重载方法

具体差别会在后面详细说明。

我们要实现一个定时任务,只需要实现TimerTask的run方法即可。每一个任务都有下一次执行时间nextExecutionTime(毫秒),如果是周期性的任务,那么每次执行都会更新这个时间为下一次的执行时间,当nextExecutionTime小于当前时间时,都会执行它。

一、使用方式

Timer的具体使用方法非常简单,比如:

Timer timer = new Timer();

timer.schedule(new TimerTask() {

@Override

public void run() {

System.out.println("Timer is running");

}

}, 2000);上面这个定时任务表示在2秒后开始执行,只执行一次。当然还可以执行周期性任务,只需要添加schedule的第三个参数period,如:

Timer timer = new Timer();

timer. scheduleAtFixedRate(new TimerTask() {

@Override

public void run() {

System.out.println("Timer is running");

}

}, 2000, 5000);表示2秒后开始执行,然后每隔5秒执行一次。

二、具体实现的分析

对于每一个Timer,后台只使用一个线程来执行所有的任务。而所有的任务都保存到一个任务队列java.util.TaskQueue中,它是Timer的一个内部类,这是一个优先级队列,使用的算法是最小二叉堆(binary min heap)。

Timer的文档中说:

After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection).

意思是(没有完全按照原文翻译):当任务队列中所有的任务都执行完后,即没有一次性执行的任务,也没有周期性的任务,那么Timer的后台线程将优雅地终止,并成为垃圾回收的对象。

但是在测试时却发现,在执行完一个一次性任务后,任务线程却不终止,而是一直阻塞,这不知道是何原因,使用的JDK是1.7.0_79。

使用jstack查看到这个线程的状态如下:

"Timer-0" prio=5 tid=0x00007ff184046000 nid=0x5807 in Object.wait() [0x000000011ebea000]

java.lang.Thread.State: WAITING (on object monitor)

at java.lang.Object.wait(Native Method)

- waiting on <0x00000007ab1bbea0> (a java.util.TaskQueue)

at java.lang.Object.wait(Object.java:503)

at java.util.TimerThread.mainLoop(Timer.java:526)

- locked <0x00000007ab1bbea0> (a java.util.TaskQueue)

at java.util.TimerThread.run(Timer.java:505)

可以看到Timer任务线程处理阻塞状态。

一个TimerTask有四种状态:

VIRGIN:新创建的任务的状态,表示还未安排执行

SCHEDULED:已安排执行,对于非周期性的任务来说,表示还未执行,当把任务添加到任务执行队列时的状态,即调用Timer.schedule时

EXECUTED:表示非周期性任务已经执行或正在执行中,并且还未被取消

CANCELLED:表示任务已经取消,当调用cancel方法后即为该状态,该状态的任务会在每次执行时被移出队列

下面看看当调用Timer.schedule时发生了什么,所有调用Timer.schedule都是调用的一个私有方法sched。

比如延迟执行的任务(非周期性任务):

public void schedule(TimerTask task, long delay) {

// 延迟不能小于0

if (delay < 0)

throw new IllegalArgumentException("Negative delay.”);

// 调用私有的sched方法,注意这里的执行时间是绝对时间,而period为0表示不是周期性任务

sched(task, System.currentTimeMillis()+delay, 0);

}

private void sched(TimerTask task, long time, long period) {

if (time < 0)

throw new IllegalArgumentException("Illegal execution time.");

// Constrain value of period sufficiently to prevent numeric

// overflow while still being effectively infinitely large.

if (Math.abs(period) > (Long.MAX_VALUE >> 1))

period >>= 1;

synchronized(queue) {

// 如果定时器已经取消,那么不能再执行任何任务,所以抛出异常

if (!thread.newTasksMayBeScheduled)

throw new IllegalStateException("Timer already cancelled.");

synchronized(task.lock) {

// 如果要执行的任务状态不是VIRGIN,那么抛出异常

if (task.state != TimerTask.VIRGIN)

throw new IllegalStateException(

"Task already scheduled or cancelled”);

// 设置任务的执行时间,注意这个时间是绝对时间

task.nextExecutionTime = time;

// 设置任务执行的周期

task.period = period;

// 设置任务的状态为SCHEDULED

task.state = TimerTask.SCHEDULED;

}

// 把任务添加到任务队列,等任务执行线程调试执行

queue.add(task);

// 检查下一个将要执行的任务是不是当前添加的任务,如果是则通知后台任务线程

if (queue.getMin() == task)

queue.notify();

}

}执行任务的后台线程,Timer使用了一个内部类TimerThread,它有一个newTasksMayBeScheduled属性表示是否还能执行任务,默认为true,当调用cancel方法时设置为false,之后将不能再添加或执行任务。同时它还持有一个任务队列的引用,而不是直接引用Timer的任务队列,这是因为为了防止循环引用导致Timer无法被垃圾回收,以及任务线程不能正常终止。

下面看一下这个任务线程的实现:

public void run() {

try {

mainLoop();

} finally {

// Someone killed this Thread, behave as if Timer cancelled

synchronized(queue) {

newTasksMayBeScheduled = false;

queue.clear(); // Eliminate obsolete references

}

}

}

实际上是调用的mainLoop方法:

private void mainLoop() {

while (true) {

try {

TimerTask task;

boolean taskFired;

synchronized(queue) {

// 等待任务队列变成非空,如果任务队列为空并且Timer未取消,就阻塞线程,等待任务队列为非空,从前面可以看到,每次添加完一个任务后,会通知这个线程来检查执行相应的任务

while (queue.isEmpty() && newTasksMayBeScheduled)

queue.wait();

if (queue.isEmpty())

break; // Queue is empty and will forever remain; die

// Queue nonempty; look at first evt and do the right thing

long currentTime, executionTime;

// 获取当前任务队列中下一个将要执行的任务

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;

// 如果当前任务的执行时间如果<=当前时间,则执行该任务,否则不执行,同时可以注意到,如果在添加任务时,执行时间是一个过去的时间,也会执行

if (taskFired = (executionTime<=currentTime)) {

// 如果不是非周期性任务,那么从任务队列中移除该任务,并把该任务的状态设置为EXECUTED

if (task.period == 0) { // Non-repeating, remove

queue.removeMin();

task.state = TimerTask.EXECUTED;

} else { // Repeating task, reschedule

// 如果是周期性任务,则重新设置任务的下一次执行时间,这里要注意period有可能为正和负

queue.rescheduleMin(

task.period<0 ? currentTime - task.period

: executionTime + task.period);

}

}

}

// 如果没有任务要执行,那么等待直到下一次任务执行的时间

if (!taskFired) // Task hasn't yet fired; wait

queue.wait(executionTime - currentTime);

}

// 如何有任务需要执行,这里才真正执行任务的逻辑

if (taskFired) // Task fired; run it, holding no locks

task.run();

} catch(InterruptedException e) {

}

}

}

其中重新设置任务的下一次执行时间的逻辑:

queue.rescheduleMin(task.period<0 ? currentTime - task.period : executionTime + task.period);这里任务执行周期可为正和负:

正数:表示按照固定的速率调度执行,比如执行周期是每5秒执行一次,如上一次执行时间是20:51:30,那么下一次执行时间就为20:51:35,如果由于执行其他任务的时间超过5秒,比如用了15秒,那么这有可能会导致这种任务不能够在指定的时间执行,这就破坏了任务执行的速率(rate),但是会在后面连续执行3次。

负数:表示按照固定的延迟来调度执行,比如执行周期是每5秒执行一次,在正常情况下,如它的执行时间是20:51:30,但是由于执行其他任务时间花了8秒秒,即执行到当前任务时是20:51:38,那么它的下一次执行时间将向后推迟,即20:51:43。

三、Timer的缺陷

1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。

2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。

3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。

四、更好的替代方法

由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值