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的更多內容!