定时且周期性的任务研究I--Timer

原文地址:http://victorzhzh.iteye.com/blog/1011061

很多时候我们希望任务可以定时的周期性的执行,在最初的JAVA工具类库中,通过Timer可以实现定时的周期性的需求,但是有一定的缺陷,例如:Timer是基于绝对时间的而非支持相对时间,因此Timer对系统时钟比较敏感。虽然有一定的问题,但是我们还是从这个最简单的实现开始研究。

 

首先,我们准备一些讨论问题的类:TimerTask1和TimerLongTask,如下


public class TimerTask1 extends TimerTask {

	@Override
	public void run() {
		String base = "abcdefghijklmnopqrstuvwxyz0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < 10; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		System.out.println(new Date()+","+sb.toString());
	}

}

这个类负责生成一个含有10个字符的字符串,这里我们将输出时间打印出来,近似认为是任务执行的时间。

public class TimerLongTask extends TimerTask {  
  
    @Override  
    public void run() {  
        System.out.println("TimerLongTask: 开始沉睡");  
        try {  
            TimeUnit.SECONDS.sleep(10);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("TimerLongTask: 已经醒来");  
    }  
  
} 

 这个类启动了一个长任务,即让任务沉睡10秒。

 下面我们来看一个定时任务执行的例子:

	public static void main(String[] args) throws InterruptedException {
		Timer timer = new Timer();
		timer.schedule(new TimerTask1(), 1);
		timer.schedule(new TimerLongTask(), 1);
		timer.schedule(new TimerTask1(), 2);
		TimeUnit.SECONDS.sleep(20);
		timer.cancel();
	}

 在这个例子中,我们先提交了一个TimerTask1任务,且让它延迟1毫秒执行,紧接着我们又提交了一个TimerLongTask长任务,且让它也延迟1毫秒执行,最后我们在提交一个TimerTask1任务,延迟2毫秒执行。然后让主线程沉睡20秒后关闭timer。我们看一下执行结果:

    Thu Apr 21 11:04:31 CST 2011,utg3hn7u4r  
    TimerLongTask: 开始沉睡  
    TimerLongTask: 已经醒来  
    Thu Apr 21 11:04:41 CST 2011,4aac22sud1  

 这里我们看到第一次输出10个字符的时间和第二次输出10个字符的时间上相差了10秒,这10秒恰恰是长任务沉睡的时间,通过这个输出我们可以分析出:Timer用来执行任务的线程其实只有一个,且逐一被执行。接下来我们查看一下源码验证一下,如下:

    private TaskQueue queue = new TaskQueue();  
    private TimerThread thread = new TimerThread(queue);  

 这两行代码来自Timer源码,我们可以看到在第一次创建了Timer时就已经创建了一个thread和一个queue,因此只有一个线程来执行我们的任务。

那么Timer是如何来执行任务的?

首先我们调用timer.schedule方法,将任务提交到timer中,Timer中有很多重载的schedule方法,但它们都会调用同一个方法即sched方法。这个方法会将我们提交的任务添加到TaskQueue的队列中(即queue),在每次添加时都会根据nextExecutionTime大小来调整队列中任务的顺序,让nextExecutionTime最小的排在队列的最前端,nextExecutionTime最大的排在队列的最后端。在创建Timer时,我们同时也创建了一个TimerThread即thread,并且启动了这个线程,

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

 TimerThread中的mainLoop方法是核心,它会完成所有的任务执行,在一开始我们的队列为空,这时mainLoop方法将会使线程进入等待状态,当我们使用schedule提交任务时会notify这个TimerThread线程,若任务的执行未到则在wait相对的时间差。

我们调整一下上面的代码,

    Timer timer = new Timer();  
    timer.schedule(new TimerTask1(), 1);  
    timer.schedule(new TimerTask1(), 5000);  
    timer.schedule(new TimerLongTask(), 3000);  
    TimeUnit.SECONDS.sleep(20);  
    timer.cancel();  
 这样先提交两个输出字符的任务最后提交长任务,在这里,我们让第二个输出字符的任务延迟5秒执行,长任务延迟3秒执行,这样得到的结果如下:

    Thu Apr 21 13:07:44 CST 2011,2sstwluvgc  
    TimerLongTask: 开始沉睡  
    TimerLongTask: 已经醒来  
    Thu Apr 21 13:07:57 CST 2011,sh4fnkqqc8  

 虽然我们改变了提交顺序,但是还是按照延迟时间递增排序执行的,两个输出字符串的时间之间相差13秒,这也是长任务等待执行时间+长任务睡眠时间之和。

 重复执行scheduleAtFixedRate方法提交任务,主要是调用rescheduleMin方法对已经调用的任务进行重新设置调度延迟,并调用fixDown方法对队列里的任务根据延迟时间重新排序。

    Timer timer = new Timer();  
    timer.scheduleAtFixedRate(new TimerTask1(), 3000, 5000);  

 3000,代表第一次执行时等待的时间,5000代表每次执行任务之间的时间间隔,运行结果:

    Thu Apr 21 13:14:55 CST 2011,izf536esrg  
    Thu Apr 21 13:15:00 CST 2011,2khzm7e09v  
    Thu Apr 21 13:15:05 CST 2011,jc3dvt2m8q  

 基本是每5秒运行一次。
 

由于Timer只使用一个线程运行所有的任务,那么当一个任务抛出运行时异常后会有什么样的情形呢?其他的任务是否可以继续?我们已经有了前面的知识可以先猜想一个结果:因为Timer只使用一个线程运行所有的任务,所以当一个线程抛出运行时异常时,这个线程就基本挂了,不会在执行后续的任何代码,因此我们可以断言,当一个任务抛出运行时异常时,后续任务都不可以执行。为了证明这个猜想,我们需要一个可以抛出异常的任务,如下:

    public class TimerExceptionTask extends TimerTask {  
      
        @Override  
        public void run() {  
            System.out.println("TimerExceptionTask: "+new Date());  
            throw new RuntimeException();  
        }  
      
    }  

 这个任务抛出一个运行时异常。接着我们需要定义一下我们任务执行的顺序:先执行一个正常的任务,然后在执行一个抛出异常的任务,最后在执行一个正常的任务,如下:

    Timer timer = new Timer();  
    timer.schedule(new TimerTask1(), 1000);  
    timer.schedule(new TimerExceptionTask(), 3000);  
    timer.schedule(new TimerTask1(), 5000);  
    TimeUnit.SECONDS.sleep(6);  
    timer.cancel();  

延迟1秒执行正常输出字符串的任务,延迟3秒执行抛出异常的任务,延迟5秒执行正常输出字符串的任务,看一下结果:

    Thu Apr 21 13:40:23 CST 2011,lk7fjneyyu  
    TimerExceptionTask: Thu Apr 21 13:40:25 CST 2011  
    Exception in thread "Timer-0" java.lang.RuntimeException  
        at org.victorzhzh.concurrency.TimerExceptionTask.run(TimerExceptionTask.java:11)  
        at java.util.TimerThread.mainLoop(Timer.java:512)  
        at java.util.TimerThread.run(Timer.java:462)  

 并没有输出两个字符串,只执行了第一次的输出字符串任务,说明当抛出运行时异常时,其后续任务不可能被执行。

鉴于Timer的缺陷,所以对它的使用还是要谨慎的,还好并发包中为我们提供了相应的替代品:ScheduledThreadPoolExecutor。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值