定时任务(1)--java.util.Timer类

在实际开发中我们肯定遇到过这样的问题,对于某项任务,需要定期执行,例如:对于一个信息管理系统,需要每天同步(更新)其他系统的一些用户信息,更新时间是在每天凌晨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类。这样能保证所有的定时任务不会相互影响。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值