定時任務(1)--java.util.Timer類

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



發佈了23 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章