疯狂Java学习笔记(67)-----------Timer和TimerTask

Timer和TimerTask

TimerTask实现了Runnable接口,待执行的任务置于run()中。Timer是一个安排TimerTask的类,两者一般一起工作。应用时首先硬创建一个TimerTask的对象,然后用一个Timer的类对象安排执行它

其实就Timer来讲就是一个调度器,而TimerTask只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现

例如这样:

<span style="font-size:18px;">Timer timer =new Timer();

timer.schedule(newTimerTask() {

        publicvoid run() {

            System.out.println("abc");

        }

},200000 , 1000);</span>

  这里直接实现一个TimerTask(当然,你可以实现多个TimerTask,多个TimerTask可以被一个Timer会被分配到多个Timer中被调度,后面会说到Timer的实现机制就是说内部的调度机制),然后编写run方法,20s后开始执行,每秒执行一次,当然你通过一个timer对象来操作多个timerTask,其实timerTask本身没什么意义,只是和timer集合操作的一个对象,实现它就必然有对应的run方法,以被调用,他甚至于根本不需要实现Runnable,因为这样往往混淆视听了,为什么呢?也是本文要说的重点。

在说到timer的原理时,我们先看看Timer里面的一些常见方法:


 

publicvoidschedule(TimerTask task,longdelay)

  

这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。


 

publicvoidschedule(TimerTask task, Date time)

 

在指定的时间点time上调度一次。


 

public void schedule(TimerTask task, long delay, long period)

 

这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。


 

publicvoidschedule(TimerTask task, Date firstTime,longperiod)

  

和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。


 

publicvoid
scheduleAtFixedRate(TimerTask task,longdelay,
long period)

  

调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法:schedule是一样的,其实不然,后面你会根据源码看到,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点,例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。


 

publicvoidscheduleAtFixedRate(TimerTask task, Date firstTime,longperiod)

  

方法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。

  接下来看源码

  首先看Timer的构造方法有几种:

  构造方法1:无参构造方法,简单通过Tiemer为前缀构造一个线程名称:

 

publicTimer() {

    this("Timer-"+ serialNumber());

}

  创建的线程不为主线程,则主线程结束后,timer自动结束,而无需使用cancel来完成对timer的结束。

  构造方法2:传入了是否为后台线程,后台线程当且仅当进程结束时,自动注销掉。

 

publicTimer(booleanisDaemon) {

    this("Timer-"+ serialNumber(),
isDaemon);

}

  另外两个构造方法负责传入名称和将timer启动:

 

publicTimer(String name,booleanisDaemon) {

      thread.setName(name);

      thread.setDaemon(isDaemon);

      thread.start();

  }

  这里有一个thread,这个thread很明显是一个线程,被包装在了Timer类中,我们看下这个thread的定义是:


 

privateTimerThread thread =newTimerThread(queue);

  而定义TimerThread部分的是:


 

classTimerThreadextendsThread

  看到这里知道了,Timer内部包装了一个线程,用来做独立于外部线程的调度,而TimerThread是一个default类型的,默认情况下是引用不到的,是被Timer自己所使用的。

  接下来看下有那些属性

  除了上面提到的thread,还有一个很重要的属性是:


 

privateTaskQueue queue =newTaskQueue();

  

看名字就知道是一个队列,队列里面可以先猜猜看是什么,那么大概应该是我要调度的任务吧,先记录下了,接下来继续向下看:

里面还有一个属性是:threadReaper,它是Object类型,只是重写了finalize方法而已,是为了垃圾回收的时候,将相应的信息回收掉,做GC的回补,也就是当timer线程由于某种原因死掉了,而未被cancel,里面的队列中的信息需要清空掉,不过我们通常是不会考虑这个方法的,所以知道java写这个方法是干什么的就行了。

  接下来看调度方法的实现:

  对于上面6个调度方法,我们不做一一列举,为什么等下你就知道了:

  来看下方法:


 

publicvoidschedule(TimerTask task,longdelay)

  的源码如下:

 

publicvoidschedule(TimerTask task,longdelay)
{

       if(delay <0)

           throw newIllegalArgumentException("Negative
delay."
);

       sche(task, System.currentTimeMillis()+delay,0);

   }

  这里调用了另一个方法,将task传入,第一个参数传入System.currentTimeMillis()+delay可见为第一次需要执行的时间的时间点了(如果传入Date,就是对象.getTime()即可,所以传入Date的几个方法就不用多说了),而第三个参数传入了0,这里可以猜下要么是时间片,要么是次数啥的,不过等会就知道是什么了;另外关于方法:sched的内容我们不着急去看他,先看下重载的方法中是如何做的

  再看看方法:


 

publicvoidschedule(TimerTask task,longdelay,longperiod)

  源码为:

 

publicvoidschedule(TimerTask task,longdelay,
long period) {

        if(delay <0)

            thrownewIllegalArgumentException("Negative
delay."
);

        if(period <=0)

            thrownewIllegalArgumentException("Non-positive
period."
);

        sched(task, System.currentTimeMillis()+delay, -period);

    }

  看来也调用了方法sched来完成调度,和上面的方法唯一的调度时候的区别是增加了传入的period,而第一个传入的是0,所以确定这个参数为时间片,而不是次数,注意这个里的period加了一个负数,也就是取反,也就是我们开始传入1000,在调用sched的时候会变成-1000,其实最终阅读完源码后你会发现这个算是老外对于一种数字的理解,而并非有什么特殊的意义,所以阅读源码的时候也有这些困难所在。

  最后再看个方法是:


 

publicvoidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod)

  源码为:

 

publicvoidscheduleAtFixedRate(TimerTask task,longdelay,
long period) {

       if(delay <0)

           thrownewIllegalArgumentException("Negative
delay."
);

       if(period <=0)

           thrownewIllegalArgumentException("Non-positive
period."
);

       sched(task, System.currentTimeMillis()+delay, period);

   }

  唯一的区别就是在period没有取反,其实你最终阅读完源码,上面的取反没有什么特殊的意义,老外不想增加一个参数来表示scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分逻辑代码一致,因此用了参数的范围来作为区分方法,也就是当你传入的参数不是正数的时候,你调用schedule方法正好是得到scheduleAtFixedRate的功能,而调用scheduleAtFixedRate方法的时候得到的正好是schedule方法的功能,呵呵,这些讨论没什么意义,讨论实质和重点:

  来看sched方法的实现体:

<span style="font-size:18px;">privatevoid sched(TimerTask task,long time, long period) {

        if(time < 0)

            thrownew IllegalArgumentException("Illegal execution time.");

        synchronized(queue) {

            if(!thread.newTasksMayBeScheduled)

                thrownew IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {

                if(task.state != TimerTask.VIRGIN)

                    thrownew IllegalStateException(

                        "Task already scheduled or cancelled");

                task.nextExecutionTime = time;

                task.period = period;

                task.state = TimerTask.SCHEDULED;

            }

            queue.add(task);

            if(queue.getMin() == task)

                queue.notify();

        }

    }</span>

  queue为一个队列,我们先不看他数据结构,看到他在做这个操作的时候,发生了同步,所以在timer级别,这个是线程安全的,最后将task相关的参数赋值,主要包含nextExecutionTime(下一次执行时间),period(时间片),state(状态),然后将它放入queue队列中,做一次notify操作,为什么要做notify操作呢?看了后面的代码你就知道了。

  简言之,这里就是讲task放入队列queue的过程,此时,你可能对queue的结构有些兴趣,那么我们先来看看queue属性的结构TaskQueue:

<span style="font-size:18px;">classTaskQueue {

    privateTimerTask[] queue = newTimerTask[128];

    privateint size = 0;</span>

  可见,TaskQueue的结构很简单,为一个数组,加一个size,有点像ArrayList,是不是长度就128呢,当然不是,ArrayList可以扩容,它可以,只是会造成内存拷贝而已,所以一个Timer来讲,只要内部的task个数不超过128是不会造成扩容的;内部提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、rescheduleMin(long
newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify();

  这里面的方法大概意思是:

  add(TimerTaskt)为增加一个任务

  size()任务队列的长度

  getMin()获取当前排序后最近需要执行的一个任务,下标为1,队列头部0是不做任何操作的。

  get(inti)获取指定下标的数据,当然包括下标0.

  removeMin()为删除当前最近执行的任务,也就是第一个元素,通常只调度一次的任务,在执行完后,调用此方法,就可以将TimerTask从队列中移除。

  quickRmove(inti)删除指定的元素,一般来说是不会调用这个方法的,这个方法只有在Timer发生purge的时候,并且当对应的TimerTask调用了cancel方法的时候,才会被调用这个方法,也就是取消某个TimerTask,然后就会从队列中移除(注意如果任务在执行中是,还是仍然在执行中的,虽然在队列中被移除了),还有就是这个cancel方法并不是Timer的cancel方法而是TimerTask,一个是调度器的,一个是单个任务的,最后注意,这个quickRmove完成后,是将队列最后一个元素补充到这个位置,所以此时会造成顺序不一致的问题,后面会有方法进行回补。

  rescheduleMin(long newTime)是重新设置当前执行的任务的下一次执行时间,并在队列中将其从新排序到合适的位置,而调用的是后面说的fixDown方法。

  对于fixUpfixDown方法来讲,前者是当新增一个task的时候,首先将元素放在队列的尾部,然后向前找是否有比自己还要晚执行的任务,如果有,就将两个任务的顺序进行交换一下。而fixDown正好相反,执行完第一个任务后,需要加上一个时间片得到下一次执行时间,从而需要将其顺序与后面的任务进行对比下。

  其次可以看下fixDown的细节为:

<span style="font-size:18px;">privatevoid fixDown(intk) {

       intj;

       while((j = k << 1) <= size && j >0) {

           if(j < size &&

               queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)

               j++;// j indexes smallest kid

           if(queue[k].nextExecutionTime <= queue[j].nextExecutionTime)

               break;

           TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;

           k = j;

       }

   }</span>
<span style="font-size:18px;">  这种方式并非排序,而是找到一个合适的位置来交换,因为并不是通过队列逐个找的,而是每次移动一个二进制为,例如传入1的时候,接下来就是2、4、8、16这些位置,找到合适的位置放下即可,顺序未必是完全有序的,它只需要看到距离调度部分的越近的是有序性越强的时候就可以了,这样即可以保证一定的顺序性,达到较好的性能。</span>

  最后一个方法是heapify,其实就是将队列的后半截,全部做一次fixeDown的操作,这个操作主要是为了回补quickRemove方法,当大量的quickRmove后,顺序被打乱后,此时将一半的区域做一次非常简单的排序即可。

  这些方法我们不在说源码了,只需要知道它提供了类似于ArrayList的东西来管理,内部有很多排序之类的处理,我们继续回到Timer,里面还有两个方法是:cancel()和方法purge()方法,其实就cancel方法来讲,一个取消操作,在测试中你会发现,如果一旦执行了这个方法timer就会结束掉,看下源码是什么呢:

<span style="font-size:18px;">publicvoid cancel() {

        synchronized(queue) {

            thread.newTasksMayBeScheduled =false;

            queue.clear();

            queue.notify(); // In case queue was already empty.

        }

    }</span>

  貌似仅仅将队列清空掉,然后设置了newTasksMayBeScheduled状态为false,最后让队列也调用了下notify操作,但是没有任何地方让线程结束掉,那么就要回到我们开始说的Timer中包含的thread为:TimerThread类了,在看这个类之前,再看下Timer中最后一个purge()类,当你对很多Task做了cancel操作后,此时通过调用purge方法实现对这些cancel掉的类空间的回收,上面已经提到,此时会造成顺序混乱,所以需要调用队里的heapify方法来完成顺序的重排,源码如下:

<span style="font-size:18px;">publicint purge() {

         intresult = 0;

         synchronized(queue) {

             for(int i = queue.size(); i > 0; i--) {

                 if(queue.get(i).state == TimerTask.CANCELLED) {

                     queue.quickRemove(i);

                     result++;

                 }

             }

             if(result != 0)

                 queue.heapify();

         }

         returnresult;

     }</span>

  那么调度呢,是如何调度的呢,那些notify,和清空队列是如何做到的呢?我们就要看看TimerThread类了,内部有一个属性是:newTasksMayBeScheduled,也就是我们开始所提及的那个参数在cancel的时候会被设置为false。

  另一个属性定义了


 

privateTaskQueue queue;

  

也就是我们所调用的queue了,这下联通了吧,不过这里是queue是通过构造方法传入的,传入后赋值用以操作,很明显是Timer传递给这个线程的,我们知道它是一个线程,所以执行的中心自然是run方法了,所以看下run方法的body部分是:

<span style="font-size:18px;">publicvoid run() {

        try{

            mainLoop();

        }finally {

            synchronized(queue) {

                newTasksMayBeScheduled =false;

                queue.clear(); // Eliminate obsolete references

            }

        }

    }</span>

  try很简单,就一个mainLoop,看名字知道是主循环程序,finally中也就是必然执行的程序为将参数为为false,并将队列清空掉。

那么最核心的就是mainLoop了,是的,看懂了mainLoop一切都懂了:

<span style="font-size:18px;">privatevoid mainLoop() {

        while(true) {

            try{

                TimerTask task;

                booleantaskFired;

                synchronized(queue) {

                    // Wait for queue to become non-empty

                    while(queue.isEmpty() && newTasksMayBeScheduled)

                        queue.wait();

                    if(queue.isEmpty())

                        break;// Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing

                    longcurrentTime, executionTime;

                    task = queue.getMin();

                    synchronized(task.lock) {

                        if(task.state == TimerTask.CANCELLED) {

                            queue.removeMin();

                            continue; // No action required, poll queue again

                        }

                        currentTime = System.currentTimeMillis();

                        executionTime = task.nextExecutionTime;

                        if(taskFired = (executionTime<=currentTime)) {

                            if(task.period == 0) {// Non-repeating, remove

                                queue.removeMin();

                                task.state = TimerTask.EXECUTED;

                            }else { // Repeating task, reschedule

                                queue.rescheduleMin(

                                  task.period<0? currentTime   - task.period

                                                : executionTime + task.period);

                            }

                        }

                    }

                    if(!taskFired) // Task hasn't yet fired; wait

                        queue.wait(executionTime - currentTime);

                }

                if(taskFired)  // Task fired; run it, holding no locks

                    task.run();

            }catch(InterruptedException e) {

            }

        }

    }</span>

  可以发现这个timer是一个死循环程序,除非遇到不能捕获的异常或break才会跳出,首先注意这段代码:

while(queue.isEmpty() &&newTasksMayBeScheduled)

                        queue.wait();

  循环体为循环过程中,条件为queue为空且newTasksMayBeScheduled状态为true,可以看到这个状态其关键作用,也就是跳出循环的条件就是要么队列不为空,要么是newTasksMayBeScheduled状态设置为false才会跳出,而wait就是在等待其他地方对queue发生notify操作,从上面的代码中可以发现,当发生add、cancel以及在threadReaper调用finalize方法的时候会被调用,第三个我们基本可以不考虑其实发生add的时候也就是当队列还是空的时候,发生add使得队列不为空就跳出循环,而cancel是设置了状态,否则不会进入这个循环,那么看下面的代码:

if(queue.isEmpty())

    break;

  当跳出上面的循环后,如果是设置了newTasksMayBeScheduled状态为false跳出,也就是调用了cancel,那么queue就是空的,此时就直接跳出外部的死循环,所以cancel就是这样实现的,如果下面的任务还在跑还没运行到这里来,cancel是不起作用的。

  接下来是获取一个当前系统时间和上次预计的执行时间,如果预计执行的时间小于当前系统时间,那么就需要执行,此时判定时间片是否为0,如果为0,则调用removeMin方法将其移除,否则将task通过rescheduleMin设置最新时间并排序:

<span style="font-size:18px;">currentTime = System.currentTimeMillis();

executionTime = task.nextExecutionTime;

if(taskFired = (executionTime<=currentTime)) {

      if(task.period == 0) {// Non-repeating, remove

           queue.removeMin();

           task.state = TimerTask.EXECUTED;

      }else { // Repeating task, reschedule

           queue.rescheduleMin(

           task.period<0? currentTime   - task.period

                              : executionTime + task.period);

     }

}</span>

  这里可以看到,period为负数的时候,就会被认为是按照按照当前系统时间+一个时间片来计算下一次时间,就是前面说的schedule和scheduleAtFixedRate的区别了,其实内部是通过正负数来判定的,也许java是不想增加参数,而又想增加程序的可读性,才这样做,其实通过正负判定是有些诡异的,也就是你如果在schedule方法传入负数达到的功能和scheduleAtFixedRate的功能是一样的,相反在scheduleAtFixedRate方法中传入负数功能和schedule方法是一样的。

  同时你可以看到period为0,就是只执行一次,所以时间片正负0都用上了,呵呵,然后再看看mainLoop接下来的部分:


 

if(!taskFired)// Taskhasn‘t yet fired; wait

    queue.wait(executionTime- currentTime);

  这里是如果任务执行时间还未到,就等待一段时间,当然这个等待很可能会被其他的线程操作add和cancel的时候被唤醒,因为内部有notify方法,所以这个时间并不是完全准确,在这里大多数情况下是考虑Timer内部的task信息是稳定的,cancel方法唤醒的话是另一回事。

  最后:


 

if(taskFired)// Task fired; run it, holding no locks

    task.run();

  如果线程需要执行,那么调用它的run方法,而并非启动一个新的线程或从线程池中获取一个线程来执行,所以TimerTask的run方法并不是多线程的run方法,虽然实现了Runnable,但是仅仅是为了表示它是可执行的,并不代表它必须通过线程的方式来执行的。

  回过头来再看看:

  TimerTimerTask的简单组合是多线程的嘛?不是,一个Timer内部包装了“一个Thread”和“一个Task”队列,这个队列按照一定的方式将任务排队处理,包含的线程在Timer的构造方法调用时被启动,这个Thread的run方法无限循环这个Task队列,若队列为空且没发生cancel操作,此时会一直等待,如果等待完成后,队列还是为空,则认为发生了cancel从而跳出死循环,结束任务;循环中如果发现任务需要执行的时间小于系统时间,则需要执行,那么根据任务的时间片从新计算下次执行时间,若时间片为0代表只执行一次,则直接移除队列即可。

  但是是否能实现多线程呢?可以,任何东西是否是多线程完全看个人意愿,多个Timer自然就是多线程的,每个Timer都有自己的线程处理逻辑,当然Timer从这里来看并不是很适合很多任务在短时间内的快速调度,至少不是很适合同一个timer上挂很多任务,在多线程的领域中我们更多是使用多线程中的:


 

Executors.newScheduledThreadPool

来完成对调度队列中的线程池的处理,内部通过newScheduledThreadPoolExecutor来创建线程池的Executor的创建,当然也可以调用


 

Executors.unconfigurableScheduledExecutorService

  方法来创建一个DelegatedScheduledExecutorService其实这个类就是包装了下下scheduleExecutor,也就是这只是一个壳,英文理解就是被委派的意思,被托管的意思。

Timer类包含的方法有:

1.Timer()

以常规方式运行task

2.Timer(boolean)

true时使用后台进程线程。只要剩下的程序记叙运行,后台进程线程就会执行。

3.public void cancel()

终止Timer的功能执行,但不会对正在执行的任务有影响。当执行cancel方法后将不能再用其分配任务。

4.public void schedule(TimerTask task,Date time)

task被安排在time指定的时间执行,如果时间为过去时则任务立刻执行。

5.public void schedule(TimerTask task, Date firstTime, long period)

task被安排在time指定的时间执行,执行后将每隔period(毫秒)反复执行。由于规定的时间间隔并不能保证与时钟精准的同不步,所以该方法最适合从短期看保持频率准确是更重要的的地方

6.public void schedule(TimerTask task, long delay)

task被安排在delay(毫秒)指定的时间后执行。

7.public void schedule(TimerTask task,long delay, long period)

task被安排在delay(毫秒)指定的时间后执行。执行后将每隔period(毫秒)反复执行。

8.public void scheduleAtFixedRate(TimerTask task,Date firstTime, long period)

task被安排在firstTime指定的时间执行。执行后将每隔period(毫秒)反复执行。每一次重复的时间时盒第一次执行而不是和前一次执行有关。因此执行的总速度是固定的。

9.public void scheduleAtFixedRate(TimerTask task,long delay,long period)

task被安排在delay(毫秒)指定的时间后执行。执行后将每隔period(毫秒)反复执行。每一次重复的时间时盒第一次执行而不是和前一次执行有关。因此执行的总速度是固定的。

TimerTask的主要方法:

1.public boolean cancel()

终止任务的执行运行。如果Timer时要求循环执行的,则如果正在执行,则执行完了就再步会循环。如果还未执行或处于停歇期,则不会执行了

2.public abstract void run()

3.public long scheduledExecutionTime()

返回被安排最后执行任务的时间。一般确定任务的当今的实行是否足够及时 ,证明进行被计划的活动为正当:

public void run() {

if (System.currentTimeMillis() - scheduledExecutionTime() >=

MAX_TARDINESS)

return;  // Too late; skip this execution.

// Perform the task

}

4.protected TimerTask()

例:

import java.util.*;
import java.io.*;

public class doTask extends TimerTask {
   String index;
   Timer myTimer = new Timer();
   public doTask(String index) {
    this.index = index;
   }

   public void run() {
    System.out.println(index);
   }

   public static void main(String args) {
    doTask myTask1 = new doTask("First task");
    myTask1.start(0,3);
    doTask myTask2 = new doTask("Second task");
    myTask2.start(0,1);

    try{
     Thread.sleep(6000);
    }
    catch(InterruptedException e){
    }   

    myTask1.end();
    myTask2.end();//程序结束时用cancel()结束Timer

   }

   public void start(int delay, int internal) {
    myTimer.schedule(this, delay * 1000, internal * 1000); //利用timer.schedule方法
   }
   public void end(){
    myTimer.cancel();
   }
}

进一步分析schedule和scheduleAtFixedRate

(1)2个参数的schedule在制定任务计划时, 如果指定的计划执行时间scheduledExecutionTime<=systemCurrentTime,则task会被立即执行。scheduledExecutionTime不会因为某一个task的过度执行而改变。

(2)3个参数的schedule在制定反复执行一个task的计划时,每一次执行这个task的计划执行时间随着前一次的实际执行时间而变,也就是scheduledExecutionTime(第n+1次)=realExecutionTime(第n次)+periodTime。也就是说如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>=scheduledExecutionTime(第n+1次),则此时不做时隔等待,立即执行第n+1次task,而接下来的第n+2次task的scheduledExecutionTime(第n+2次)就随着变成了realExecutionTime(第n+1次)+periodTime。说白了,这个方法更注重保持间隔时间的稳定。

(3)3个参数的scheduleAtFixedRate在制定反复执行一个task的计划时,每一次执行这个task的计划执行时间在最初就被定下来了,也就是scheduledExecutionTime(第n次)=firstExecuteTime+n*periodTime;如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>=scheduledExecutionTime(第n+1次),则此时不做period间隔等待,立即执行第n+1次task,而接下来的第n+2次的task的scheduledExecutionTime(第n+2次)依然还是firstExecuteTime+(n+2)*periodTime这在第一次执行task就定下来了。说白了,这个方法更注重保持执行频率的稳定。

一些注意的问题

  • 每一个Timer仅对应唯一一个线程。
  • Timer不保证任务执行的十分精确。
  • Timer类的线程安全的。

输出:

First task

Second task

Second task

Second task

First task

Second task

Second task

Second task

本文借鉴于:

http://www.cnblogs.com/xuling/archive/2011/06/06/2073864.html

http://blog.csdn.net/xieyuooo/article/details/8607220

时间: 2024-08-30 07:47:48

疯狂Java学习笔记(67)-----------Timer和TimerTask的相关文章

【疯狂Java学习笔记】【理解面向对象】

[学习笔记]1.Java语言是纯粹的面向对象语言,这体现在Java完全支持面向对象的三大基本特征:封装.继承.多态.抽象也是面向对象的重要组成部分,不过它不是面向对象的特征之一,因为所有的编程语言都需要抽象. 2.面向对象开发方法比较结构化开发方法的优势在于可以提供更好的可重用性.可扩展性.可维护性. 3.基于对象和面向对象的区别:基于对象也使用了对象,但是无法通过现有的对象作为模板来生成新的对象类型,继而产生新的对象,也就是说,基于对象没有继承的特点.而面向对象有继承,而多态则是建立在继承的基

疯狂java学习笔记之面向对象(八) - static和final

一.static: 1.static是一个标识符: - 有static修饰的成员表明该成员是属于类的; - 没有static修饰的成员表明该成员是属于实例/对象的. 2.static修饰的成员(Field.方法.初始化块),与类共存亡:static修饰的成员建议总是通过类名来访问,虽然它也可以通过实例来访问(实质也是通过类来访问的),所以平时若在其他程序中见到通过实例/对象来访问static成员时,可以直接将实例/对象 替换成类名: 3.程序都是先有类再有对象的,有可能出现有类但没有实例/对象的

疯狂Java学习笔记(34)----------Iterator、Collection接口以及foreach

Iterator.Collection接口: 如下图:Iterator.Collection同在一个包中: 红字部分使我们经常遇到的,但是遇到又不知道怎么去理解,去应用它! Collection是最基本集合接口,它定义了一组允许重复的对象.Collection接口派生了两个子接口Set和List,分别定义了两种不同的存储方式,如下: 2. Set接口 Set接口继承于Collection接口,它没有提供额外的方法,但实现了Set接口的集合类中的元素是无序且不可重复. 特征:无序且不可重复. 3.

疯狂Java学习笔记(51)-----------面试题

自己做了一点面试题,感觉很经典,分享给大家,发现还有很多东西需要学! 一.String,StringBuffer, StringBuilder 的区别是什么?String为什么是不可变的? 答:   1.String是字符串常量,StringBuffer和StringBuilder都是字符串变量.后两者的字符内容可变,而前者创建后内容不可变. 2.String不可变是因为在JDK中String类被声明为一个final类. 3.StringBuffer是线程安全的,而StringBuilder是非

疯狂Java学习笔记(87)-----------十篇必读的Java文章

1.Brian Goetz:"管理工作:发人深省的部分" 这其实不是一篇博文,而是Brian Goetz对于甲骨文公司Java的管理的一个非常有趣的讨论的记录.在 以前我们将Java语言与Scala或者Ceylon相比较的时候,对其1-2个特性一直稍微有些意见. 对于为什么Java尽快变得和其他语言一样"时髦"不是一个好主意,Brian提出了很好的观点.每一个Java开发者都应有所了解(大约一个小时). Youtube链接. 2.Aleksey Shipilёv:(

疯狂Java学习笔记(88)-----------值得拥有的10本书

Java是时下最流行的编程语言之一.市面上也出现了适合初学者的大量书籍.但是对于那些在Java编程上淫浸多时的开发人员而言,这些书的内容未免显得过于简单和冗余了.那些适合初学者的书籍看着真想打瞌睡,有木有.想找高级点的Java书籍吧,又不知道哪些适合自己. 别急,雪中送炭的来了:下面我将分享的书单绝对值得拥有.ps,我也尽力避免列出为特定软件或框架或认证的Java书,因为我觉得那不是纯Java书. 1.<Java in a Nutshell>(Java技术手册) 与其说是必读书籍,还不说是参考

疯狂Java学习笔记(89)-----------Java习惯用法总结

在Java编程中,有些知识 并不能仅通过语言规范或者标准API文档就能学到的.在本文中,我会尽量收集一些最常用的习惯用法,特别是很难猜到的用法.(Joshua Bloch的<Effective Java>对这个话题给出了更详尽的论述,可以从这本书里学习更多的用法.) 我把本文的所有代码都放在公共场所里.你可以根据自己的喜好去复制和修改任意的代码片段,不需要任何的凭证. 目录 实现: equals() hashCode() compareTo() clone() 应用: StringBuilde

疯狂Java学习笔记(52)-----------Annotation(注释)第一篇

从JDK1.5开始,Java中增加了对元数据(MetaData)的支持,也就是Annotation(注释),这种Annotation与Java程序中的单行注释和文本注释是有一定区别,也有一定联系的.其实,我们现在说的Annotation是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理.通过Annotation,程序开发人员可以在不改变原来逻辑的情况下,在源文件嵌入一些补充的信息.代码分析工具,开发工具和部署工具可通过这些补充信息进行验正或者部署. Annotatio

疯狂Java学习笔记(70)-----------挚爱Java

与大家分享! 挚爱Java 10个使用Java最广泛的现实领域 写好Java代码的30条经验总结 Java字符串的substring真的会引起内存泄露么? Java内存的原型及工作原理深度剖析 Java 8中HashMap的性能提升 Java内存的原型及工作原理深度剖析 请不要说自己是Java程序猿 Java程序猿必须掌握的8大排序算法 推荐.国外程序猿整理的Java资源大全 编程开发 10个强大的纯CSS3动画案例分享 前端project师的神器Sublime Text使用介绍 浅谈SQL语句