Java簡單實現定時器
本文實例為大傢分享瞭Java簡單實現定時器的具體代碼,供大傢參考,具體內容如下
一、定時器
定時器相當於一個任務管理器。有些任務可能現在執行, 有些任務可能過1個小時,甚至很久才會執行。定時器就是對這些任務進行管理監視, 如果一個任務執行時間到瞭,定時器就會將這個任務執行。 保證所有的任務都會在合適的時間執行。
二、定時器的實現
對於定時器的實現,我們可以劃分為3個部分。
1、 使用一個Task類描述每一個任務(裡面包含任務的執行方法, 定時時間)。
2、 使用優先級隊列管理這些任務類。
2.1 我們都知道優先級隊列底層實現是堆(以小根堆為例), 堆頂的元素是所有的元素的最小值。 我們以任務的定時時間為比較原則構建, 這樣就可以保證堆頂元素的任務執行時間是最短的(這樣的實現,我們需要在Task類內部定義比較規則-即重寫Comparable接口的CompareTo方法)。
2.2 當一個任務執行完畢, 就會從優先級隊列取出poll掉, 然後內部重新組織保證新的堆頂元素是定時時間最短的。
2.3 如果說堆頂的任務定時時間還沒有到達(當然後續的任務定時時間肯定會更長,不會被執行)
3、使用一個線程循環掃描優先級隊列, 相當於一個監控線程,循環判斷堆頂任務是否滿足執行時間。
三、定時器的組成
1、制定任務類Task
Task類包含任務的 執行方法 和 定時時間。
1.1 執行方法我采用封裝Runnable中run方法實現, 這樣做是為瞭後續添加任務時方便寫執行邏輯。
1.2 定時時間就是long類型的變量
1.3 制定比較規則, 後續優先級隊列中存放的是Task對象(而在內部構建時,需要比較兩個Task對象的),對於對象的比較, 我們以對象的定時時間為規則, 制定小根堆。
static class Task implements Comparable<Task>{ //Runnable類中有一個run方法, 通過這個方法實現任務的執行 private Runnable command; //time表示執行的時間 private long time; //構造方法 public Task(Runnable command, long time) { this.command = command; this.time = System.currentTimeMillis() + time; //將時間轉化為絕對時間 } //執行任務的邏輯 public void run() { command.run(); } //定義比較方法 - 方便後續的優先級隊列構建 @Override public int compareTo(Task o) { return (int)(this.time - o.time); } }
2、監管線程&定時器對象Timer
監管線程Worker中包含優先級隊列(小根堆)queue 和 循環監管的流程。
Timer對象封裝瞭監管線程Woker 和 任務的添加方法schedule()
關於監管線程的優化
2.1 循環監控存在一個弊端,那就是一直循環判斷, 占用CPU資源。
(假如堆首任務的執行是1小時後, 再次期間監管線程會跑1小時循環判斷。)
解決方法: 可以通過線程阻塞和喚醒來解決。在下面代碼有詳細註釋和實現。
2.1.1 如果任務1小時後執行, 我們讓監管線程wait(1小時), 但在此期間如果有新的任務添加進來(可能新的任務需要等30分鐘就可以執行,堆首元素發生變化) ,這時需要喚醒監管線程來重新判斷。(由於wait和notify方法不在用一個類中實現, 我們通過一個Object(mailBox)來阻塞、喚醒)
//檢測線程, 繼承Thread類,重寫內部run方法,屬於線程的創建方法之一。 static class Worker extends Thread { //優先級隊列 - JUC包裡面 private PriorityBlockingQueue<Task> queue = null; //為瞭對監管線程進行阻塞和喚醒,采用同一對象 private Object mailBox = null; //構造函數 public Worker(PriorityBlockingQueue<Task> queue, Object mailBox) { this.queue = queue; this.mailBox = mailBox; } @Override public void run() { //實現具體的執行邏輯 while(true) { try { //1、取優先級隊列的隊首元素 Task task = queue.peek(); //2、比較隊首的元素的時間是否大於當前時間 if(task == null) { continue; } long curTime = System.currentTimeMillis(); if(task.time > curTime) { //時間還沒有到, 由於取出瞭任務, 需要重新放置回去 //優化1: 空循環等待 - wait(time) 讓線程休眠time時間,然後在執行 // 如果在等待期間有新的任務添加, 這個時候我們喚醒線程, 繼續判斷(因為存在新的時間過短需要立即執行) // 這個隻需要添加一個新任務時, 喚醒即可 //優化2: 訪問隊首元素而不是取出, 防止無所謂的刪除、插入。(維護優先級隊列是有消耗的) long gapTime = task.time - curTime; synchronized (mailBox) { mailBox.wait(gapTime); } } else { //直接執行 //如果執行到瞭, 則會刪除頭部元素, 調用任務的執行過程。 task = queue.take(); task.run(); } } catch(InterruptedException e) { e.printStackTrace(); break; } } } } //定時器簡單實現 static class Timer { //定時器的實現步驟 //1、用一個類描述任務 //2、用優先級隊列管理這些任務, 比較方法通過任務的制定時間,每次取隊首元素 // 隊首元素是執行時間最近的 private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>(); //3、用一個線程來循環掃描當前的阻塞隊列,判斷隊首的執行時間, 如果執行時間到瞭,那就執行。 //4、創建一個Object對象,用於設置線程阻塞使用的, 存在線程阻塞, 添加任務時喚醒的操作 private Object mailBox = new Object(); //構造函數 public Timer() { //創建線程 Worker worker = new Worker(queue, mailBox); worker.start(); } //4、提供一個方法, 讓調用者能夠把任務安排起來 public void schedule(Runnable command, long time) { Task task = new Task(command, time); queue.put(task); synchronized (mailBox) { mailBox.notify(); } } }
3、測試代碼
其中添加瞭4個任務, 分別是2s、5s、7s、10s後執行。
public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("郝夢武一號任務執行, 執行代號:閃電; 定時時間:2s"); } }, 2000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("郝夢武二號任務執行, 執行代號:暴風; 定時時間:5s"); } }, 5000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("郝夢武三號任務執行, 執行代號:狂風; 定時時間:7s"); } }, 7000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("郝夢武三號任務執行, 執行代號:地震; 定時時間:10s"); } }, 10000); }
4、測試結果
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。