在实际开发中我们肯定遇到过这样的问题,对于某项任务,需要定期执行,例如:对于一个信息管理系统,需要每天同步(更新)其他系统的一些用户信息,更新时间是在每天凌晨1:00.对于这样的需求,可以说是经常遇到的。只不过间隔时间可能不一样,起止执行时间不同罢了。对于这样的需求,我们一般叫他们为“定时任务”。对于定时任务的实现,也可以有多种方法。我在最初接触定时任务需求时,当时的jdk版本为1.4。实现也不是由我实现的,但是我对这个问题很感兴趣,所以也就关注了一下。
实现方法主要是通过java.util.Timer类来实现的。这个类提供了类库级的实现类。下面就简单介绍一下这个类。
我的学习方法,就是不管怎么样,先搞一个例子出来,然后慢慢玩儿。下面我就先弄一个例子出来。
public class TimerImpl {
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output1:"+System.currentTimeMillis());
}
};
timer.schedule(task1, 1000, 3000);
}
}
我们新建一个TimerImpl类。创建一个执行任务的方法TestTimer()方法,Timer对象需要调用TimerTask对象来实现自动任务。在自动任务执行时,会调用注册的TimerTask类中的run()方法(注意区分与Thread类的run()方法,后面会提到定时任务关于线程的问题。)。 timer.schedule(task1, 1000, 3000);是只从启动开始,1000ms(1s)后开始执行task1的run()方法,每隔3000ms(3s)执行一次。在我本地的执行结果为:
Output1:1407652478887
Output1:1407652481888
Output1:1407652484889
Output1:1407652487890
通过结果可以看出来,每隔3秒会执行一次task任务。
这是一个最初的实现,下面我们就拿起镐头,往Timer类的"祖坟"上刨一下。
这是单个的任务,是否支持多个任务呢。
我们继续写代码,很多语言描述不清的问题,几行代码和一个运行结果,就会显得明显很多。
我们修改我们最初的代码为:
public class TimerImpl {
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output1:"+System.currentTimeMillis());
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("Output2:"+System.currentTimeMillis());
}
};
timer.schedule(task1, 1000, 3000);
timer.schedule(task2, 1000, 3000);
}
}
即为Timer类添加一个task2任务,也就是说,一个Timer类,将会管理和调度两个定时任务。
说多了都是扯淡,run一下试试(注意:两个定时任务输出结果的标记)。
运行结果为:
Output1:1407653280310
Output2:1407653280310
Output2:1407653283311
Output1:1407653283311
Output1:1407653286312
Output2:1407653286312
Output2:1407653289313
Output1:1407653289313
虽然两个任务的执行顺序不一定,但总体来说,还是成对出现的。就是两个任务都可以在要求的时间点执行。在理想情况下,这个也是可以接受的的。这里说的理想情况,是指task任务可以在瞬间执行完毕。不需要耗费太多的时间。
但是现实往往是残酷的。很多任务是不能"瞬间"完成的。并且可能存在任务执行时间比间隔时间更长的"恶劣环境"。
在理想环境下,Timer和TimerTask的配合是可以帮我们做很多事情的。但在恶劣环境下,我们不知道究竟怎么样。下面我们就测试看看:
1、首先看一下任务执行时长大于定时任务执行间隔的情况。
直接上代码,运行完再解释。
public class TimerImpl {
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output1:"+System.currentTimeMillis());
try {
Thread.currentThread().sleep(4000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
timer.schedule(task1, 1000, 3000);
}
}
在代码中,我们在任务执行过程中,要求任务执行线程睡眠4s,而任务间隔是3s,即任务执行时间大于任务间隔时间。
运行一下试试。
Output1:1407653879177
Output1:1407653883178
Output1:1407653887179
Output1:1407653891180
Output1:1407653895180
我们发现,任务的执行间隔成了4s,也就是说,我们设置的任务运行间隔时间,没有起作用。在这种“恶劣环境”下,Timer的工作并没有按照我们的要求来工作,但总体来说,还是可以理解的。毕竟,一次任务执行完成后,才执行下一次任务,还是可以理解的。
但下面的测试,我们真的就理解不了了。code:
public class TimerImpl {
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output1:"+System.currentTimeMillis());
try {
Thread.currentThread().sleep(4000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("Output2:"+System.currentTimeMillis());
}
};
timer.schedule(task1, 1000, 3000);
timer.schedule(task2, 1000, 3000);
}
}
运行结果:
Output1:1407654103643
Output2:1407654107645
Output1:1407654107645
Output1:1407654111646
Output2:1407654115646
Output1:1407654115646
Output1:1407654119647
Output2:1407654123648
Output1:1407654123648
oh! My God!
出了什么事情,为什么task2被屏蔽了这么多。本来应该3s执行一次的task2,几乎只输出了一半,也就是说将近有一般的task2任务没有执行。
究竟发生了什么,为什么会这样,Oh! My God。幸亏我试了一下,要不然项目直接上线,我还得去逛逛智联,即使我不想去,我老板肯定也会让我去的。
下面我们就分析一下。
通过查阅一些东西,发现,Timer是单线程的(这么说不准确,但是易于理解)。也就是说,当一个任务执行完毕后,才会去找下一次最近需要执行的任务,而这个时候,task2已经被错过一个了,只能放弃了,不执行了。就是这么简单。遇到了问题,就得解决问题,方法总比问题多。既然是线程的问题。那就用线程解决它吧。我们试一下。
public class TimerImpl {
private class ThreadTimerImpl implements Runnable{
int i = 0;
ThreadTimerImpl(int i){
this.i = i;
}
@Override
public void run() {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output"+i+":"+System.currentTimeMillis());
if (1 == i) {
try {
Thread.currentThread().sleep(4000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
timer.schedule(task1, 1000, 3000);
}
}
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Thread t1 = new Thread( new ThreadTimerImpl(1) );
Thread t2 = new Thread( new ThreadTimerImpl(2) );
t1.start();
t2.start();
}
}
运行结果:
Output2:1407655177409
Output1:1407655177409
Output2:1407655180410
Output1:1407655181411
Output2:1407655183411
Output1:1407655185411
Output2:1407655186412
Output1:1407655189412
Output2:1407655189413
上述代码表示,每个Timer独占一个线程,并且每隔Timer只有一个task任务。不包含一个Timer对应多个task的情况。这样,就保证了在第一个任务阻塞(由于一些原因不能保证按照规定时间运行的情况)时,保证另外一个任务可以按时执行。
事实证明:这样做是可以的。但是问题又来了,每隔Timer独占一个线程,是不是有点太奢侈了,我们也不确定,task的执行线程,与Timer的线程是同意个线程。即存在这种可能timer创建一个线程,来执行task。我们来测试一下。
public class TimerImpl {
public static void main(String [] args){
TimerImpl timerImpl = new TimerImpl();
timerImpl.TestTimer();
}
//测试方法
void TestTimer(){
Timer timer1 = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("Output1:"+System.currentTimeMillis());
try {
Thread.currentThread().sleep(4000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
timer1.schedule(task1, 1000, 3000);
Timer timer2 = new Timer();
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("Output2:"+System.currentTimeMillis());
}
};
timer2.schedule(task2, 1000, 3000);
}
}
跑完的结果是:
Output2:1407655761262
Output1:1407655761262
Output2:1407655764263
Output1:1407655765263
Output2:1407655767264
Output1:1407655769263
Output2:1407655770265
Output1:1407655773264
通过上面所有测试的结果,我们大概可以明白Timer的原理了:
每个Timer类,在调用schedule(TimerTask task, long delay, long period)
方法后,会产生并且只产生一个线程,来操作该Timer类加载到的task任务,即使会有多个task任务,也会在同一个线程里执行。多个task之间,会有影响。所以在有多个定时任务时,应该每个定时任务对应一个task,每隔task对应一个Timer类。这样能保证所有的定时任务不会相互影响。