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

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

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

Java代码  

  1. public class TimerTask1 extends TimerTask {
  2. @Override
  3. public void run() {
  4. String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  5. Random random = new Random();
  6. StringBuffer sb = new StringBuffer();
  7. for (int i = 0; i < 10; i++) {
  8. int number = random.nextInt(base.length());
  9. sb.append(base.charAt(number));
  10. }
  11. System.out.println(new Date()+","+sb.toString());
  12. }
  13. }

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

Java代码  

  1. public class TimerLongTask extends TimerTask {
  2. @Override
  3. public void run() {
  4. System.out.println("TimerLongTask: 开始沉睡");
  5. try {
  6. TimeUnit.SECONDS.sleep(10);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println("TimerLongTask: 已经醒来");
  11. }
  12. }

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

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

Java代码  

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

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

Java代码  

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

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

Java代码  

  1. private TaskQueue queue = new TaskQueue();
  2. 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,并且启动了这个线程,

Java代码  

  1. public Timer(String name) {
  2. thread.setName(name);
  3. thread.start();
  4. }

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

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

Java代码  

  1. Timer timer = new Timer();
  2. timer.schedule(new TimerTask1(), 1);
  3. timer.schedule(new TimerTask1(), 5000);
  4. timer.schedule(new TimerLongTask(), 3000);
  5. TimeUnit.SECONDS.sleep(20);
  6. timer.cancel();

这样先提交两个输出字符的任务最后提交长任务,在这里,我们让第二个输出字符的任务延迟5秒执行,长任务延迟3秒执行,这样得到的结果如下:

Java代码  

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

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

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

Java代码  

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

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

Java代码  

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

基本是每5秒运行一次。

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

Java代码  

  1. public class TimerExceptionTask extends TimerTask {
  2. @Override
  3. public void run() {
  4. System.out.println("TimerExceptionTask: "+new Date());
  5. throw new RuntimeException();
  6. }
  7. }

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

Java代码  

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

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

Java代码  

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

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

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

时间: 2024-10-15 09:43:37

定时且周期性的任务研究I--Timer的相关文章

定时且周期性的任务研究II--ScheduledThreadPoolExecutor

http://victorzhzh.iteye.com/blog/1011635 上一篇中我们看到了Timer的不足之处,本篇我们将围绕这些不足之处看看ScheduledThreadPoolExecutor是如何优化的. 为了研究方便我们需要两个类: Java代码   public class Task1 implements Callable<String> { @Override public String call() throws Exception { String base = &

JavaScript定时机制setTimeout与setInterval研究

JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout(function() { alert('你好!'); }, 0); setInterval(callbackFunction, 100); 认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是JavaScript API文档明确定义第二个参数意义为隔多少毫秒后,回调方法就会被

TCP/IP研究(1)-Timer(2)

Function Partition of TCP Timer in ASIC and Firmware Date Author Description 2006-08-24 Kevin Initialization TCP maintains the connection state internally and requires timers to keep track of events. TCP maintains seven timers for each connection: l 

Java 中几种常用的线程池

Java 中几种常用的线程池 转载 : https://www.cnblogs.com/sachen/p/7401959.html 原创 2016年04月14日 23:29:01 标签: java / 线程池 / Executor 878 概述: 在java内置API中操作线程所用到的类为Thread.创建线程一般有两种方式, 继承Thread方式 实现Runnable方式,并以runnable作为target创建Thread 在Android中的耗时任务一般都需要另开线程来执行,常常需要用线程

Java定时任务:利用java Timer类实现定时执行任务的功能

一.概述 在java中实现定时执行任务的功能,主要用到两个类,Timer和TimerTask类.其中Timer是用来在一个后台线程按指定的计划来执行指定的任务. TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务,具体要执行的代码写在TimerTask需要被实现的run方法中. 二.先看一个最简单的例子 我们通过代码来说明 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Timer:定时

1.Timer基础1.1Timer简介1.2Timer实战2.Timer的定时调度函数2.1schedule2.1.1 schedule(task,time)2.1.2 schedule(task,time,period)2.1.3 schedule(task,delay)2.1.4 schedule(task,delay,period)2.2scheduleAtFixedRate的两种用法2.2.1 scheduleAtFixedRate(task,time,period)2.2.2 sche

多线程中sleep和wait的区别,以及多线程的实现方式及原因,定时器--Timer

1.  Java中sleep和wait的区别 ① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类. sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep. ② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法. sleep不出让系统资源:wait是进入线程等待池等待,出让系统资源,其他线程可以占用C

细说java.util.Timer

Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示.任务的执行方式有两种: 按固定速率执行:即scheduleAtFixedRate的两个重载方法 按固定延迟执行:即schedule的4个重载方法 具体差别会在后面详细说明. 我们要实现一个定时任务,只需要实现TimerTask的run方法即可.每一个任务都有下一次执行时间nextExecutionTime(毫秒),如果是周期性的任务,那么每次执行都会更新这个时间为下一次的执行时间,当n

Spring整合定时任务调度框架Quartz实战

定时的任务处理在程序开发中应用的相当普遍,之前一直使用JDK的Timer类库来做任务调度功能不是很方便,因为它不能像cron服务那样可以指定具体年.月.日.时和分的时间,我们只能将时间通过换算成微秒后传给它,而在quartz中我们只需要设置cronExpression就可以完成定时的周期性的方法调用.Quartz中最核心的是任务调度器Scheduler,它负责管理Job,Trigger和 Calendar,而每一个Job就是一个需要执行任务的java类,在Schelduler调度任务时 执行的就