天天看點

高并發--卷5--任務排程:Timer計時器

在多線程技術中,用的較多的就是Timer計時器了,它本身不是Runnable的實作類,計劃任務用另一個TimerTask類來實作。

應用場景:比如在報表統計中常常需要使用任務排程來更新報表庫 。

Timer.schedule(TimerTask,Date)

我們用Timer的schedule方法來設定一個任務,Date為任務的執行時間。Timer.schedule(TimerTask,long),當第二個參數是long類型的時候,代表延遲自執行時間,當存在第三個參數的時候,代表周期。比如Timer.schedule(TimerTask,long,long)
public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務執行了-" + new Date());
            }
        };
        String timeStr = "2018-7-22 17:29:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date time = sdf.parse(timeStr);
        timer.schedule(task, time);
    }
}
           

輸出結果:

任務執行了-Sun Jul 22 18:09:01 CST 2018

  • 發現任務立即執行,這也就說明了如果任務設定時間早于目前時間,就立即執行任務。

    我們再來看任務時間晚于目前時間的情況:

這裡寫代碼片public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務執行了-" + new Date());
            }
        };
        String timeStr = "2018-7-22 18:13:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date time = sdf.parse(timeStr);
        timer.schedule(task, time);
    }
}
           

輸出結果:

任務執行了-Sun Jul 22 18:13:00 CST 2018

一個Timer設定多個Task

public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務1執行了-" + new Date());
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務2執行了-" + new Date());
            }
        };
        String timeStr = "2018-7-22 18:19:00";
        String timeStr2 = "2018-7-22 18:20:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date time = sdf.parse(timeStr);
        Date time2 = sdf.parse(timeStr2);
        timer.schedule(task, time);
        timer.schedule(task2, time2);
    }
}
           

輸出結果:

任務1執行了-Sun Jul 22 18:19:00 CST 2018

任務2執行了-Sun Jul 22 18:20:00 CST 2018

Timer定時器不會自動結束

當new Timer()之後,發現程序不會自動結束。因為Timer不是一個守護線程,main線程結束之後,它不會自動結束。

來看一下Timer的構造函數:

public Timer(String name) { thread.setName(name); thread.start(); }

方法一:設定為守護線程

public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        Timer timer = new Timer(true);
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務1執行了-" + new Date());
            }
        };
        System.out.println("現在時間-"+ new Date());
        String timeStr = "2018-7-22 19:16:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date time = sdf.parse(timeStr);
        timer.schedule(task, time);
    }
}
           

輸出結果:

現在時間-Sun Jul 22 19:15:36 CST 2018

然後就直接結束了

發現如果設定成守護線程,那麼主線程結束了,該任務也就得不到執行了。

方法二:停止等待

使用該方法,可以讓主程式等待Task任務執行完成之後才執行後面的語句。這就讓Timer起到了一個計時器的效果。
import java.text.ParseException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class T{
    private static boolean flag = true;
    public static void main(String[] args) throws InterruptedException, ParseException {
        Timer timer = new Timer();
        final ReentrantLock lock = new ReentrantLock();
        final Condition condition = lock.newCondition();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務1執行了-" + System.currentTimeMillis());
                try{
                    lock.lock();
                }finally{
                    condition.signal();
                    flag = false;
                    lock.unlock();                  
                }
            }
        };
        System.out.println("現在時間-"+ System.currentTimeMillis());
        timer.schedule(task, ); //設定到2000ms後執行該任務
        try{
            lock.lock();
            while(flag){
                condition.await();
            }
        }finally{
            lock.unlock();
        }
        timer.cancel();
        System.out.println("任務執行完了,執行後面的語句");       
    }
}
           
以上的做法,完全可以在一個線程中完成,也就是說,我們完全可以讓main線程sleep(2000)再來執行後面的語句。

方法三:直接釋放Timer(Timer.cancle)

完全可以在task中調用timer的cancle()方法實作停止線程。
public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        final Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務執行-" + System.currentTimeMillis());
                timer.cancel();
            }
        };
        timer.schedule(task, );
        System.out.println("main語句-"+System.currentTimeMillis());
    }
}
           
顯然這種做法更加合理。但是要值得注意的是,Timer.cancle()會清楚計時器内所有的task,并且停止timer中的線程等待垃圾回收器回收。

TimerTask.cancle()

清空目前任務,取消後續的周期執行。如果目前任務是周期性的,當調用TimerTask.cancle()方法後,它将得不到後續的周期性執行。
public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        final Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務A執行-" + System.currentTimeMillis());
                this.cancel();  //下個周期不會執行
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務B執行-" + System.currentTimeMillis());
            }
        };
        //2000ms後執行,并且以1000ms為周期繼續執行
        timer.schedule(task, ,);
        timer.schedule(task2, ,);
    }
}
           

輸出結果:

任務A執行-1532269174240

任務B執行-1532269174240

任務B執行-1532269175240

任務B執行-1532269176241

任務B執行-1532269177241

周期時間小于任務執行時間

當周期時間小于任務執行時間的時候,schedule()的周期改成任務執行的時間為周期。例如,任務執行時間為3000ms,周期為2000ms,那麼周期将會變成3000ms。
public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        final Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務A執行-" + new Date());
                try {
                    Thread.sleep();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //2000ms後執行,并且以2000ms為周期繼續執行
        timer.schedule(task, ,);
    }
}
           

輸出結果:

任務A執行-Mon Jul 23 00:24:19 CST 2018

任務A執行-Mon Jul 23 00:24:22 CST 2018

任務A執行-Mon Jul 23 00:24:25 CST 2018

任務A執行-Mon Jul 23 00:24:28 CST 2018

Timer.schedule()和Timer.scheduleAtFixedRate()

值得注意的是,如果計劃的時間設定在過去,那麼就會立即執行。Timer.schedule()和Timer.scheduleAtFixedRate()的都是設定計劃,那麼它們的差別在哪呢?
public class T{
    public static void main(String[] args) throws InterruptedException, ParseException {
        final Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務A執行-" + new Date());
            }
        };
        TimerTask task2 = new TimerTask(){
            @Override
            public void run() {
                System.out.println("任務B執行-" + new Date());
            }
        };
        //2000ms後執行,并且以1000ms為周期繼續執行
        System.out.println(new Date());
        String date = "2018-07-23 14:06:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date time = sdf.parse(date);
        timer.schedule(task, time, **);
        timer.scheduleAtFixedRate(task2, time, **);
    }
}
           

輸出結果:

Mon Jul 23 14:07:21 CST 2018

任務A執行-Mon Jul 23 14:07:21 CST 2018

任務B執行-Mon Jul 23 14:07:21 CST 2018

任務B執行-Mon Jul 23 14:09:00 CST 2018

任務A執行-Mon Jul 23 14:10:21 CST 2018

任務B執行-Mon Jul 23 14:12:00 CST 2018

任務A執行-Mon Jul 23 14:13:21 CST 2018

  • 輸出結果分析:

    我們設定的時間是 06分,但是現在的時間是07分,但是我們發現09的時候B計劃執行了,也就是說Timer.scheduleAtFixedRate()設定的3分鐘周期是從06分開始的,而Timer.schedule()設定的3分鐘周期是從07分開始的。

  • 差別:

    scheduleAtFixedRate()是從計劃時間開始計算,schedule()是從第一次執行器開始計算。

    共同點是,如果任務時間設定到過去,那麼都會立即執行。

    這裡還應該注意一點,如果過去時間過去太久,已經經過了好幾個周期,scheduleAtFixedRate()會追趕彌補,schedule()不會追趕彌補

  • 應用場景

    A的資料量極大,B庫的資料來自于A庫,我們提取A庫中的資料進行統計制作報表,如果每次檢視報表都去A庫中提取,效率很低,這時候需要定時從A庫中周期的提取資料到B庫,這時候可以用到該計時器設定TimerTask定時執行任務。