在實際開發中我們肯定遇到過這樣的問題,對於某項任務,需要定期執行,例如:對於一個信息管理系統,需要每天同步(更新)其他系統的一些用戶信息,更新時間是在每天凌晨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類。這樣能保證所有的定時任務不會相互影響。