首頁  >  文章  >  Java  >  Java定時任務:利用java Timer類別實現定時執行任務的功能

Java定時任務:利用java Timer類別實現定時執行任務的功能

高洛峰
高洛峰原創
2017-01-11 16:07:533562瀏覽

一、概述

在java中實現定時執行任務的功能,主要用到兩個類,Timer和TimerTask類。其中Timer是用來在一個後台執行緒按指定的計畫來執行指定的任務。

TimerTask一個抽象類,它的子類別代表一個可以被Timer計畫的任務,具體要執行的程式碼寫在TimerTask需要被實作的run方法中。

二、先看一個最簡單的例子

我們透過程式碼來說明

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
    Thread.sleep(1000*5); //休眠5秒
    System.out.println("main  end:"+getCurrentTime());
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 0);
  }
}

為了便於透過列印觀察訊息,我們在main方法中加了些列印訊息,並呼叫Thread.sleep讓主執行緒休眠一下。另外在類別中增加了一個取得目前日期的getCurrentTime方法。

上面的程式碼,在startTimer方法中,先建立了一個TimerTask物件(將要被計時器執行的任務),然後建立了一個Timer對象,然後呼叫Timer類別的schedule方法。 Timer類別有多個具有不同參數的schedule方法。這裡用到的是: 

public void schedule(TimerTask task, long delay)

該方法的含義是,表示定時器將延遲delay(毫秒)時間後,執行task任務。如果delay為負數或0,則任務會立即進行。而且是一次性的執行任務,後續不會重複(或定時)執行該任務。

對於Timer類,也提供一個同樣功能的方法,如下: 

public void schedule(TimerTask task, Date time)

該方法與上面方法的區別是,上面方法是指定延期一段時間執行,這個方法是指定在某個特定的時間點執行。請注意,如果系統的目前時間已經超過了參數time指定的時間,則該任務會立即執行。

當執行上面程式碼時,我們發現程式立即列印類似如下的2個訊息:

main start:2016-01-13 22:23:18
task   run:2016-01-13:18

task   run:2016-01-13:2233:18

因為我們這裡給schedule方法傳遞的delay參數值為0,所以任務會立即執行,所以兩個語句印出來的時間是一樣的,這是應該的。大家可以自己改變傳入的delay值來看輸出訊息的變化。再過大約5秒(即sleep的時間)後,繼續印了1個訊息:

main   end:2016-01-13 22:23:23

列印訊息的時間與上面語句差了5秒,與sleep設定的一致,也是很合理的。

但我們會發現一個很有趣的現象,會發現該進程不會退出,這時main主線程已經結束了,這說明定時器把任務完成後,即使後面沒有待等待執行的任務了,定時器中建立的後台執行緒也不會立即退出。查看了相關的java doc文檔,解釋說定時器線程不會主動退出,需要等待垃圾回收,但java的待垃圾回收是無法透過程式碼自己控制的,而是由虛擬機器控制的。

研究了下,發現在創建Timer對象,及執行Timer timer = new Timer(); 語句時,定時器線程就會被創建。也就是說即使上面程式碼沒有timer.schedule(task, 0);這個語句,程式也不會退出。感覺這個挺不合理的。再次研究了下Timer類別的原始程式碼,發現其還有一個帶有布林參數的建構子:

public Timer(boolean isDaemon)

從參數名稱就可以看出,如果參數值為true時,則Timer建立的計時器線程為守護線程。守護線程的意思是,當java進程中所有的工作線程都退出後,守護線程就自動退出了。

這時我們只要把上面例子中的創建Timer對象的程式碼改為:Timer timer = new Timer(true);

發現運行程序後,等main線程(main線程不是守護線程,是工作線程)結束後,程式會退出,也就是說定時器線程也退出了,說明加上參數true後,創建的它是守護線程了。

但問題是,在真正的應用場景中,有很多工作執行緒在運行,程式不會隨便退出。那如果要想定時器能立即退出或關閉,該怎麼辦呢?這個我們下面介紹。

三、定時器的退出


Timer類別提供了一個cancel方法可以取消計時器。呼叫cancel方法會終止此計時器,丟棄所有目前已排程的任務。這不會幹擾目前正在執行的任務(如果存在)。一旦終止了計時器,那麼它的執行緒也會終止,並且無法根據它安排更多的任務。 

注意,在此計時器所呼叫的計時器任務的 run 方法內呼叫此方法,就可以絕對確保正在執行的任務是此計時器所執行的最後一個任務。可以重複呼叫此方法;但是第二次和後續呼叫無效。

我們再看一個範例程式碼:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    Timer timer = startTimer();
    Thread.sleep(1000*5); //休眠5秒
    System.out.println("main  end:"+getCurrentTime());
    timer.cancel();
  }
 
  public static Timer startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 0);
    return timer;
  }
}

運行程序,跟上面一個例子的輸出情況完全一樣。差別是,當main方法結束後。進程會主動退出,也就是說定時器執行緒已經關閉了。

因為我們在main方法中呼叫了cancel方法。 注意,如果不是在TimerTask的run方法中呼叫cancel方法一定要注意,請務必確保希望執行的任務已經開始執行或執行完畢,否則如果任務還未開始執行。就呼叫cancel,則所有任務都不會被執行了。例如上面的程式碼,🎜

比如上面的代码,如果我们不在main方法中调用cancel方法,而是在startTimer方法中 timer.schedule(task, 0); 语句后加上timer.cancel();语句,运行后会发现,定时器任务不会被执行,因为还未来得及执行就被取消中止了。

四、定时执行任务

上面的例子,我们介绍的是一次性任务,也就是定时器时间到了,执行完任务,后面不会再重复执行。在实际的应用中,有很多场景需要定时重复的执行同一个任务。这也分两种情况,一是每隔一段时间就执行任务,二是每天(或每周、每月等)的固定某个(或某几个)时间点来执行任务。

我们先来看第一种情况,实现每隔10秒执行同一任务的例子。代码如下:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
        try {
          Thread.sleep(1000*3);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 1000*5,1000*10);
  }
}

执行上述程序,输出信息如下(因为定时器没有停止,重复执行任务,会不断输出,这里只拷贝了前面的一些输出)

main start:2016-01-14 08:41:14
task   run:2016-01-14 08:41:19
task   run:2016-01-14 08:41:29
task   run:2016-01-14 08:41:39
task   run:2016-01-14 08:41:49
task   run:2016-01-14 08:42:00
task   run:2016-01-14 08:42:10
task   run:2016-01-14 08:42:20
task   run:2016-01-14 08:42:30
task   run:2016-01-14 08:42:40

在上面的代码中,我们调用了 timer.schedule(task, 1000*5,1000*10); 这个含义是该任务延迟5秒后执行,然后会每隔10秒重复执行。我们观察输出信息中打印的时间,是与预期一样的。 另外可以看出,间隔是以任务开始执行时间为起点算的,也就是并不是任务执行完成后再等待10秒。

Timer类有两个方法可以实现这样的功能,如下:

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

我们上面代码用的是第一个方法。两个方法区别在于第一次执行的时间,第一个方法是在指定延期一段时间(单位为毫秒)后执行;第二个方法是在指定的时间点执行。

这时我们考虑如下场景,如果某个任务的执行耗时超过了下次等待时间,会出现什么情况呢? 我们还是通过代码来看:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task begin:"+getCurrentTime());
        try {
          Thread.sleep(1000*10);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("task  end:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 1000*5,1000*5);
  }
}

与前面代码相比,我们只改了2处代码和修改了下打印,一是将run方法中的sleep改为了10秒,二是将任务的执行周期改为5秒。也就说任务的执行耗时超过了任务重复执行的间隔。运行程序,前面的输出如下:

main start:2016-01-14 09:03:51
task begin:2016-01-14 09:03:56
task   end:2016-01-14 09:04:06
task begin:2016-01-14 09:04:06
task   end:2016-01-14 09:04:16
task begin:2016-01-14 09:04:16
task   end:2016-01-14 09:04:26
task begin:2016-01-14 09:04:26
task   end:2016-01-14 09:04:36
task begin:2016-01-14 09:04:36
task   end:2016-01-14 09:04:46
task begin:2016-01-14 09:04:46
task   end:2016-01-14 09:04:56

可以看出,每个任务执行完成后,会立即执行下一个任务。因为从任务开始执行到任务完成的耗时已经超过了任务重复的间隔时间,所以会重复执行。

五、定时执行任务(重复固定时间点执行)

我们来实现这样一个功能,每天的凌晨1点定时执行一个任务,这在很多系统中都有这种功能,比如在这个任务中完成数据备份、数据统计等耗时、耗资源较多的任务。代码如下:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:" + getCurrentTime());
    startTimer();
  }
 
  public static void startTimer() {
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task begin:" + getCurrentTime());
        try {
          Thread.sleep(1000 * 20);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("task  end:" + getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, buildTime(), 1000 * 60 * 60 * 24);
  }
 
  private static Date buildTime() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 1);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    Date time = calendar.getTime();
    if (time.before(new Date())) {
      //若果当前时间已经是凌晨1点后,需要往后加1天,否则任务会立即执行。
      //很多系统往往系统启动时就需要立即执行一次任务,但下面又需要每天凌晨1点执行,怎么办呢?
      //很简单,就在系统初始化话时单独执行一次任务(不需要用定时器,只是执行那段任务的代码)
      time = addDay(time, 1);
    }
    return time;
  }
 
  private static Date addDay(Date date, int days) {
    Calendar startDT = Calendar.getInstance();
    startDT.setTime(date);
    startDT.add(Calendar.DAY_OF_MONTH, days);
    return startDT.getTime();
  }
 
}

因为是间隔24小时执行,没法等待观察输出。

六、小结

本文介绍了利用java Timer类如何执行定时任务的机制。可以看出,还是有许多需要注意的方法。 本文中介绍的例子,每个定时器只对应一个任务。

本文介绍的内容可以满足大部分应用场景了,但还有一些问题,比如对于一个定时器包括多个任务?定时器取消后能否再次添加任务?Timer类中还有哪些方法可用? 这些问题,我们再后面的博文中介绍。

更多Java定时任务:利用java Timer类实现定时执行任务的功能相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn