Java:如何正确使用Timer




Java:如何正确使用Timer

在需要按时间计划执行简单任务的情况下,Timer是最常被使用到的工具类。使用Timer来调度TimerTask的实现者来执行任务,有两种方式,一种是使任务在指定时间被执行一次,另一种是从某一指定时间开始周期性地执行任务。
 
下面是一个简单的Timer例子,它每隔10秒钟执行一次特定操作doWork。
Timer timer = new Timer();
                        TimerTask  task = new TimerTask (){
                        public void run() {
                             doWork();
                           }
};
              timer.schedule (task, 10000L, 10000L);
可以看到,具体的任务由TimerTask的子类实现,Timer负责管理、执行TimerTask。
 
Timer 的使用
在不同的场景下,需要使用不同的Timer接口。如上所说,主要区分两种情况
1) 在指定时间执行任务,只执行一次
-          public void schedule(TimerTask task, long delay)
-          public void schedule(TimerTask task, Date time)
 
2)从指定时间开始,周期性地重复执行,直到任务被cancel掉。其中又分两种类型:
2.1) 一种是按上一次任务执行的时间为依据,计算本次执行时间,可以称为相对时间法。比如,如果第一次任务是1分10秒执行的,周期为5秒,因系统繁忙(比如垃 圾回收、虚拟内存切换),1分15秒没有得到机会执行,直到1分16秒才有机会执行第二次任务,那么第3次的执行时间将是1分21秒,偏移了1秒。
-          public void schedule(TimerTask task, long delay, long period)
-          public void schedule(TimerTask task, Date firstTime, long period)
 
2.2) 另一种是绝对时间法,以用户设计的起始时间为基准,第n次执行时间为“起始时间+n*周期时间”。比如,在上面的情况下,虽然因为系统繁忙,第二执行时间被推后1秒,但第3次的时间点仍然应该是1分20秒。
-          public void scheduleAtFixedRate(TimerTask task, long delay, long period)
-          public void scheduleAtFixedRate(TimerTask task, Date firstTime,                                    long period)
 
相 对时间法,关注于满足短时间内的执行间隔,绝对时间法,则更关注在一个长时间范围内,任务被执行的次数。如果我们要编写一个程序,用timer控制文档编 辑器中提示光标的闪烁,用哪种更合适? 当然是相对时间法。如果改用绝对时间法,当从系统繁忙状态恢复后,光标会快速连续闪烁多次,以弥补回在系统繁忙期间没有被执行的任务,这样的情况会用户来 说比较难以接受。又如,每10分钟检查一次新邮件的到来,也适合于使用相对时间法。
Timer timer = new Timer();
                        TimerTask  task = new TimerTask (){
                         public void run() {
                                displayCursor();
                           }
};
              timer.schedule (task, 1000L, 1000L); //每秒闪烁一次光标
 
作为对比,我们来考虑一种绝对时间法的应用场景——倒数任务,比如,要求在10秒内做倒数计时,每秒做一次doworkPerSecond操作,10秒结束时做一次doworkEnd操作,然后结束任务。
Timer timer = new Timer();
                        TimerTask  task = new TimerTask (){
                              private int count=10;
                              public void run() {
                                   if(count>0){
                                      doWorkPerSecond();
                                     count--;
                            }else{
                               doWorkEnd();
                               cancel();
                             }
                           }
};
                        timer. scheduleAtFixedRate (task, 1000L, 1000L);
Timer及相关类的内部实现
Timer的内部会启动一个线程TimerThread。即使有多个任务被加入这个Timer,它始终只有一个线程来管理这些任务。
-          TimerThread是Thread的子类。加入Timer的所有任务都会被最终放入TimerThread所管理的TaskQueue中。 TimerThread会不断查看TaskQueue中的任务,取出当前时刻应该被执行的任务执行之,并且会重新计算该任务的下一次执行时间,重新放入 TaskQueue。直到所有任务执行完毕(单次任务)或者被cancel(重复执行的任务),该线程才会结束。
-          TaskQueue,由数组实现的二叉堆,堆的排序是以任务的下一次执行时间为依据的。二叉堆的使用使得TimerThread以简洁高效的方式快速找到 当前时刻需要执行的TimerTask,因为,堆排序的特性是保证最小(或者最大)值位于堆叠顶端,在这里,queue[1]始终是下次执行时间 (nextExecutionTime)最小的,即应该最先被执行的任务
 
比如,同一个timer管理两个任务task1和task2
timer.schedule (task1, 4000L, 10000L);
timer. scheduleAtFixedRate (task2, 2000L, 15000L);
则,TaskQueue 中会有两个任务:task1和task2。task2会排在头部queue[1],当task2执行时间到,task2被执行,同时修改其 nextExecutionTime =当前的nextExecutionTime +15000L(绝对时间法)并重新在二叉堆中排序。排序后,task1被放到头部。当task1执行时间到,task1被执行,并修改其 nextExecutionTime =当前时间+10000L,然后重新在二叉堆中对其排序………
 
一个例子 
当收到客户端请求时,服务端生成一个Response对象。服务端希望客户端访问该对象的间隔时间不能超过20秒,否则,服务端认为客户端已经异常关闭或者网络异常,此时销毁掉该对象并打印错误日志。每次访问都会重新开始计时。
 
class Response{
private TimerTask  timeout;
            public void init(){
         ………
Timer timer = new Timer();
timeout = new TimeOutTask();
                        timer.schedule (timeout, 20000L);
}
 
public void invoke(){
    timeout.cancel();//取消当前的timeout任务
; ….
   timeout = new TimeOutTask();
                           timer.schedule (timeout, 20000L);//重新开始计时
}
 
void destroy(){
   ……..
}
 
class TimeOutTask extends TimerTask{
           public void run() {
TraceTool.error(“Time out, destroy the Response object.”);
destroy();
                           }
}
}
 
因为Timer不支持对任务重置计时,所以此处采取了先cancel当前的任务再重新加入新任务来达到重置计时的目的。注意,对一个已经cancel的任务,不能通过schedule重新加入Timer中执行。TimerTask的状态机如下:
 

一个新生成的TimerTask其状态为VIRGIN,Timer只接受状态为VIRGIN的任务,否则会有IllegalStateException异常抛出。
调用任务的cancel方法,该任务就转入CANCELLED状态,并很快从TaskQueue中删除。对单次执行的任务,一旦执行结束,该任务也会从中删除。这意味着TimerTask将不再被timer所执行了。


va实现定时任务的三种方法

  在应用里经常都有用到在后台跑定时任务的需求。举个例子,比如需要在服务后台跑一个定时任务来进行非实时计算,清除临时数据、文件等。在本文里,我会给大家介绍3种不同的实现方法:
  • 普通thread实现
  • TimerTask实现
  • ScheduledExecutorService实现

普通thread

这是最常见的,创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public   class   Task1 {
public   static   void   main(String[] args) {
   // run in a second
   final   long   timeInterval =  1000 ;
   Runnable runnable =  new   Runnable() {
   public   void   run() {
     while   ( true ) {
       // ------- code for task to run
       System.out.println( "Hello !!" );
       // ------- ends here
       try   {
        Thread.sleep(timeInterval);
       catch   (InterruptedException e) {
         e.printStackTrace();
       }
       }
     }
   };
   Thread thread =  new   Thread(runnable);
   thread.start();
   }
}

 

用Timer和TimerTask

上面的实现是非常快速简便的,但它也缺少一些功能。
用Timer和TimerTask的话与上述方法相比有如下好处:

  • 当启动和去取消任务时可以控制
  • 第一次执行任务时可以指定你想要的delay时间

在实现时,Timer类可以调度任务,TimerTask则是通过在run()方法里实现具体任务。
Timer实例可以调度多任务,它是线程安全的。
当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import   java.util.Timer;
import   java.util.TimerTask;
public   class   Task2 {
   public   static   void   main(String[] args) {
     TimerTask task =  new   TimerTask() {
       @Override
       public   void   run() {
         // task to run goes here
         System.out.println( "Hello !!!" );
       }
     };
     Timer timer =  new   Timer();
     long   delay =  0 ;
     long   intevalPeriod =  1   1000 ;
     // schedules the task to be run in an interval
     timer.scheduleAtFixedRate(task, delay,
                                 intevalPeriod);
   // end of main
}

 

ScheduledExecutorService

ScheduledExecutorService是从Java SE 5的java.util.concurrent里,做为并发工具类被引进的,这是最理想的定时任务实现方式。
相比于上两个方法,它有以下好处:

  •  相比于Timer的单线程,它是通过线程池的方式来执行任务的
  •  可以很灵活的去设定第一次执行任务delay时间
  •  提供了良好的约定,以便设定执行的时间间隔

 我们通过ScheduledExecutorService#scheduleAtFixedRate展示这个例子,通过代码里参数的控制,首次执行加了delay时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import   java.util.concurrent.Executors;
import   java.util.concurrent.ScheduledExecutorService;
import   java.util.concurrent.TimeUnit;
public   class   Task3 {
   public   static   void   main(String[] args) {
     Runnable runnable =  new   Runnable() {
       public   void   run() {
         // task to run goes here
         System.out.println( "Hello !!" );
       }
     };
     ScheduledExecutorService service = Executors
                     .newSingleThreadScheduledExecutor();
     service.scheduleAtFixedRate(runnable,  0 1 , TimeUnit.SECONDS);
   }
}

 

 







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值