JAVA定时器Timer&TimerTask剖析

         在项目开发中,难免会遇到一些定时任务。比如邮件定时发送,则可以定义一个定时器,每隔1秒扫描一次定时邮件表,取出时间等于当前时间的邮件,然后进行发送。当然B/S项目都使用了一些框架,而有些框架也提供有定时器功能,在这我要介绍的时JDK提供的定时器Timer。

 

       学习一个JDK中自带的类,第一件事打开API,看看Timer类的描述及提供了哪些接口。



      上面出现的守护线程,读者不明白的话,可以看一下我上一篇博文,关于用户线程和守护线程的介绍。

 

 

     看出,Timer类中的我们最主要使用的就是schedule()的各个重载方法了,而schedule()方法要我们必须传入一个TimerTask对象。那么再来看看TimerTask的API情况。

 

     看到TimerTask类实现了Runnable接口,说明这是一个线程。

     看到这里,那么可以下一个初步结论:Timer就是一个调度器,它将TimerTask(一个Timer可以管理多个TimerTask)收集起来,定时时间到了,Timer就让TimerTask线程启动。

 

    Timer和TimerTask都已经有所了解了,那么写一个简单的例子看看效果。

 

定时任务类

package timer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class MyTimerTask extends TimerTask{

	private String name;
	public MyTimerTask(String name){
		this.name=name;
	}
	@Override
	public void run() {
		System.out.println(name+"在执行....."+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
	}

}


定时执行任务类的测试类

package timer;

import java.util.Timer;

public class TimerTest {
	public static void main(String[] args) {
		Timer t=new Timer();
		t.schedule(new MyTimerTask("A"), 1000,2000);//1秒后执行该任务,然后每2秒执行1次
		t.schedule(new MyTimerTask("B"),1000,3000);
	}
}


运行结果:

A在执行.....2013-05-11 11:04:10
B在执行.....2013-05-11 11:04:10
A在执行.....2013-05-11 11:04:12
B在执行.....2013-05-11 11:04:13
A在执行.....2013-05-11 11:04:14
A在执行.....2013-05-11 11:04:16
B在执行.....2013-05-11 11:04:16
A在执行.....2013-05-11 11:04:18

 

      执行结果打印的时间,同一个任务的间隔时间可能不等于你设置的间隔时间,这就涉及到CPU对线程的执行是轮换执行的,这方面的知识,读者可以去了解多线程方面的知识。

 

      下面我们一起来浏览一下相关源码。我们要使用Timer就首先要new一个,则先看看Timer的构造方法源码。

public Timer() {
    this("Timer-" + serialNumber());
}

public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

public Timer(String name) {
    thread.setName(name);
    thread.start();
}

public Timer(String name, boolean isDaemon) {
    thread.setName(name);
    thread.setDaemon(isDaemon);
    thread.start();
}

       发现实例化Timer对象时,它会启动一个TimerThread类的线程,那继续看看TimerThread类中的run方法。

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) {//锁住定时任务队列
                // Wait for queue to become non-empty
                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)) {
                        if (task.period == 0) { // Non-repeating, remove
                            queue.removeMin();//任务只执行一次,就是在使用Timer的schedule()方法时,
                                                //没有指定每隔几秒执行一次任务,则默认执行一次,然后就从队列中删除
                            task.state = TimerTask.EXECUTED;
                        } else { // Repeating task, reschedule
                            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) {
        }
    }
}
}


      到这为止,已经知道new Timer()时JVM就会干哪些事情了。那接下来看看Timer类中schedule()方法。

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }


    这只是schedule()重载方法中的一个,但是所有的schedule()重载方法里面都会调用sched()这个方法,接下来看看sched()方法。上面有个疑问,队列进入阻塞后,谁来唤醒呢?呵呵,看下面源码

private void sched(TimerTask task, long time, long period) {
    if (time < 0)//如果指定的执行时间<0就抛出异常
        throw new IllegalArgumentException("Illegal execution time.");

    synchronized(queue) {//锁住任务队列
        if (!thread.newTasksMayBeScheduled)//检查任务的定时器是否还在,如果被取消了,则任务不能加入队列
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task.lock) {//锁住即将要加入队列的任务
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException(
                    "Task already scheduled or cancelled");
            task.nextExecutionTime = time;//任务的执行时间
            task.period = period;//任务第一次执行后,每隔多长时间执行第二次,如果period<=0,在任务执行一次后,就被取消,从队列中删除
            task.state = TimerTask.SCHEDULED;
        }

        queue.add(task);//定时任务加入队列
        if (queue.getMin() == task)//获得队列中的头任务
        	//还记得上面给出的mainLoop()方法的源码吗?
        	//如果队列为空但即将有任务进来,队列就阻塞wait();
        	//或者头任务的执行时间还没到,队列也阻塞wait();
            queue.notify();//唤醒队列,有新任务进来了
    }
}


 

 

 
 
 
 
 
 
 
 
 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值