Java中的定時器Timer詳解

簡單來說,定時器就相當於一個“鬧鐘”,給定時器設定一個任務,約定這個任務在xxx時間之後執行~

Timer類提供瞭一個核心接口,schedule(安排) 指定一個任務交給定時器,在一定時間之後再去執行這個任務~

如何實現定時器的效果~

  • Timer中要包含一個Task類,每個Task就表示一個具體的任務實例,Task裡面包含一個時間戳(啥時候執行這個任務),還包含一個Runnable實例(用來表示任務具體是啥)。
  • Timer裡面通過一個帶優先級的阻塞隊列,來組織如幹個task。
  • 這裡的優先級是按照時間的先後來排優先級,快要到時間的任務優先級更高。
  • 也就是 給 堆 加上 wait/notify ,讓它能夠帶阻塞效果。
  • 標準庫裡提供這樣的隊列PriorityBlockingQueue
  • Timer 中還需要一個專門的線程,讓這個線程不停的掃描隊首元素,看看隊首元素是不是已經可以執行瞭,如果可以執行瞭,就執行,反之繼續在隊列中等待。

具體如何用代碼實現這樣一個定時器Timer:

一般去設定時間的時候,傳入的時間,都是一個時間間隔

例如:傳入1000 ,就代表從當前開始過1000ms之後在執行;

而我這裡為瞭後面代碼方便判斷,在這裡記錄一下絕對時間,這樣this.time裡就是一個標志的ms級時間戳瞭,後續隻需要獲取當前時間戳在和這裡的time對比一下就好瞭。

在這裡插入圖片描述

Task 要放到一個優先級隊列中,但是優先級隊列裡面是需要比較優先級的,所以可以讓Task類實現Comparable接口,重寫compareTo方法來進行比較。

在這裡插入圖片描述

這裡希望時間較小的排在見面。

在這裡插入圖片描述

這裡獲取一下當前時間currTime,如果當前時間大於等於task裡約定的時間,超過說明時間到,執行任務,反之沒到,把取去的

任務再放回隊列,繼續等待。

在這裡插入圖片描述

這裡還涉及到一個問題:

舉個例子:假如你早上定瞭一個8.30的 鬧鐘,8點的時候你醒瞭,看瞭下時間,還沒到,你就繼續睡,

但是這裡是while(true),就意味著每過一秒鐘就要看一次鬧鐘,8.01看一次,8.02看一次,這樣就會白白的浪費一些資源,這就出現瞭“盲等”,在等待任務時間的過程中,一直持有著CPU資源~

所以這裡就需要優化一下:使用wait/notify機制,就可很好的改善盲等問題~

如果取出任務發現還沒到時間,就wait,等待一定時間,這裡使用的wait()的重載版本,wait()裡寫一個參數,達到等待時間,自動醒過來~ 此時就大大降低瞭掃描次數和成本,

在這裡插入圖片描述

這裡的notify(),就是保證當線程中如果有線程在WAITING狀態的線程,就需要顯示的喚醒一下線程。

舉個例子;

如果隊首元素8.30在執行,等待30分鐘,但是此時,可能突然插入一個任務,讓你8.10的時候去幹一件事,如果你8.30再去喚醒的話,8.10的任務就來不及瞭!

所以每次插入新任務的時候,都喚醒一下woeker線程,讓worker線程重新獲取一下隊首元素,看看接下來的任務等待多少時間合適。

在這裡插入圖片描述

//簡單定時器
public class TestTimer {
    //每個 Task 實例 就包含一個要執行的任務
    //Task 要放到一個優先級隊列中,但是優先級隊列裡面是需要比較優先級的
    static class Task implements Comparable<Task>{
        //什麼時候執行
        private long time;
        //執行什麼任務
        private  Runnable command;

        public Task(Runnable command ,long time){
            this.command = command;
            this.time = System.currentTimeMillis()+time;
        }

        public void  run(){
        //執行Runable 裡面的run方法
            command.run();
        }

        @Override
        public int compareTo(Task o) {
            return (int)(this.time - o.time);
        }
    }
    static class Timer{
        //先創建一個帶優先級的阻塞隊列
        private PriorityBlockingQueue<Task>  queue = new PriorityBlockingQueue<>();

        //用這個對象來完成線程之間的協調任務
        private Object meilbox = new Object();

        //schedule 方法就是把一個Task 放在Timer中
        public void schedule(Runnable command,long after){
            Task task = new Task(command,after);
            //將當前任務放入對列
            queue.put(task);
            //當worker 線程中包含wait機制的時候,在安排任務的時候就需要顯示的喚醒一下
            synchronized (meilbox){
                meilbox.notify();
            }
        }
        //寫一個構造方法,創建線程
        public Timer(){
            //創建一個線程,讓這個線程去掃描隊列的隊首元素,看看能不能執行
            Thread worker = new Thread(){
                @Override
                public void run() {
                    //取出隊首元素,判斷這個元素能不能執行
                    while(true){
                        try {
                            Task task = queue.take();
                            long currTime = System.currentTimeMillis();
                            if(currTime >= task.time){
                                //時間到,執行任務
                                task.run();
                            }else{
                                //時間沒到,繼續等待
                                queue.put(task);
                                synchronized (meilbox){
                                    meilbox.wait(task.time-currTime);
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            worker.start();
        }
    }
}

測試:

public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable command = new Runnable() {
            @Override
            public void run() {
                System.out.println("時間到瞭~");
               // timer.schedule(this,3000); 每隔3是就執行一次
            }
        };
        System.out.println("安排任務");
        timer.schedule(command,3000);
    }
}

安排任務後,等待3s就可以執行瞭

在這裡插入圖片描述

這裡補充一下Timer原生類中的一些方法

  • schedule(TimerTask task, Date time)   在指定的日期執行一次TimerTask任務;如果日期time早於當前時間,則立刻執行。
  • schedule(TimerTask task, long delay, long period)   以當前時間為基準,延遲指定的毫秒後,再按指定的時間間隔地無限次數的執行TimerTask任務。
  • schedule(TimerTask task, Date firstTime, long period)   在指定的日期之後,按指定的時間間隔地無限次數的執行TimerTask任務。
  • scheduleAtFixedRate(TimerTask task, long delay, long period)   以當前時間為基準,延遲指定的毫秒後,再按指定的時間間隔周期性地無限次數的執行TimerTask任務。

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: