在需要定时并且周期执行任务时,在最初的JAVA工具类库中,Timer可以实现任务的定时周期执行的需求,不过有一定的缺陷,比如,Timer是基于绝对时间而非相对时间,因此Timer对系统时钟比较敏感,本文就是讨论的Timer定时周期执行任务的问题。
代码清单1:TimerPrinter.java
package org.4spaces;
import java.util.Date;
import java.util.Random;
import java.util.TimerTask;
public class TimerPrinter extends TimerTask {
@Override
public void run() {
String base = "timerprinterbeginprinting.....";
Random random = new Random();
StringBuffer sb = new StringBuffer();
int number = 0;
for (int i = 0; i < 10; i++) {
number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
System.out.println(new Date() + "," + sb.toString());
}
}
以上代码的功能是:输出代码执行时间和长度为10的字符串,这里我们将输出时间打印出来,近似认为是任务执行的时间。。
代码清单2:TimerLongTask.java
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
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:已经醒来");
}
}
以上代码的功能:启动了一个长任务,让任务沉睡10s。
代码清单3:TimerTest.java
package org.4spaces;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
public class TimerTest {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerPrinter(), 1);
timer.schedule(new TimerLongTask(), 1);
timer.schedule(new TimerPrinter(), 2);
TimeUnit.SECONDS.sleep(20);
timer.cancel();
}
}
以上代码的功能:定时执行任务,我们先提交了一个打印任务,且让它延迟1毫秒执行,紧接着我们又提交了一个TimerLongTask长任务,且让它也延迟1毫秒执行,最后我们再提交一个打印任务,延迟2毫秒执行。然后让主线程沉睡20秒后关闭timer。执行结果如下:
Sat Nov 01 18:05:49 CST 2014,rigtr.meit
TimerLongTask:开始沉睡
TimerLongTask:已经醒来
Sat Nov 01 18:05:59 CST 2014,rgnneg.nni
从执行结果来看:第一次打印任务我们让延迟一秒后执行,沉睡任务我们也是设置的延迟一秒执行,第二次打印任务我们设置的是延迟2秒执行,执行结果第一次打印和第二次打印间隔为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();
}
那么逐一执行的次序又是怎么样的呢?
我们来调整一下代码:
代码清单4:TimerTest.java
package org.4spaces;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
public class TimerTest {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerPrinter(), 1);
timer.schedule(new TimerPrinter(), 2);
timer.schedule(new TimerLongTask(), 1);
TimeUnit.SECONDS.sleep(20);
timer.cancel();
}
}
执行结果:
Sat Nov 01 18:19:34 CST 2014,e.pmn.nimn
TimerLongTask:开始沉睡
TimerLongTask:已经醒来
Sat Nov 01 18:19:44 CST 2014,bnrrn..tgp
我们看到,打印任务2在沉睡任务执行之后再执行。
实际上,timer.schedule()这个方法,会将我们添加的任务,按照等待执行的时间长短排成队列,等待时间短的排在前面。
重复执行任务由方法:timer.scheduleAtFixedRate()来完成,比如:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerPrinter(), 3000, 5000);
表示,第一次执行时等待3s,然后每个5s执行一次该任务。
由于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
。
参考文章:http://victorzhzh.iteye.com/blog/1011061;
Java中Timer和ThreadPoolExecutor学习比较系列文章:
- Java中Timer和ThreadPoolExecutor的区别和比较 ;
- Java中使用Timer和TimerTask完成定时执行任务 ;
- Java中ScheduledThreadPoolExecutor实现定时周期性任务 ;
- 详细了解Java中定时器Timer的使用及缺陷分析 ;
最新评论
我的是ipv4网络,如何使用直播源啊!
我今天试了,不想啊,我的是新疆昌吉移动的网络。
收不到验证码电报
现在充值29起了