一、概述
在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
public Timer(boolean isDaemon)從參數名稱就可以看出,如果參數值為true時,則Timer建立的計時器線程為守護線程。守護線程的意思是,當java進程中所有的工作線程都退出後,守護線程就自動退出了。 這時我們只要把上面例子中的創建Timer對象的程式碼改為:Timer timer = new Timer(true);發現運行程序後,等main線程(main線程不是守護線程,是工作線程)結束後,程式會退出,也就是說定時器線程也退出了,說明加上參數true後,創建的它是守護線程了。 但問題是,在真正的應用場景中,有很多工作執行緒在運行,程式不會隨便退出。那如果要想定時器能立即退出或關閉,該怎麼辦呢?這個我們下面介紹。 三、定時器的退出
Timer類別提供了一個cancel方法可以取消計時器。呼叫cancel方法會終止此計時器,丟棄所有目前已排程的任務。這不會幹擾目前正在執行的任務(如果存在)。一旦終止了計時器,那麼它的執行緒也會終止,並且無法根據它安排更多的任務。
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方法中调用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中文网!