实现定时器
目錄
1.定時器是什么
2.標準庫中的定時器
3.實現定時器
1.定時器是什么
定時器也是軟件開發中的一個重要組件 . 類似于一個 " 鬧鐘 ". 達到一個設定的時間之后 , 就執行某個指定好的代碼。 🍃1.定時器是一種實際開發中非常常用的組件 . 🍃2.比如網絡通信中 , 如果對方 500ms 內沒有返回數據 , 則斷開連接嘗試重連 . 🍃3.比如一個 Map, 希望里面的某個 key 在 3s 之后過期 ( 自動刪除 ). 🍃4.類似于這樣的場景就需要用到定時器 .2.標準庫中的定時器
public class TestDemo1 {public static void main(String[] args) throws InterruptedException {// java.util 里的一個組件Timer timer = new Timer();// schedule : 安排一個任務// 該任務不是立刻執行,而是延遲多少時間后再執行!!timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("這是一個要執行的任務!");}},3000);// 定時器 和 sleep 不相同while(true) {System.out.println("main");Thread.sleep(1000);}} }🍁【注意事項】?
🍃1.定時器的核心方法是 schedule, schedule 包含兩個參數,第一個參數指定即將要執行的任務,第二個參數指定多長時間后執行(單位:毫秒)。
🍃2.將定時器和 sleep 做區分,sleep 是使當前線程處于阻塞狀態,而 定時器 只是記錄了多長時間后該執行的任務,中間的這些時間,當前線程該干嘛就干嘛。
🍃3.schedule 里面的 TimerTask 其實就相當于 Runnable,只不過是 TimerTask 實現了 Runnable 接口,在這里我們直接把它當成 Runnable 就好了。
3.實現定時器
通過觀察標準庫中的定時器,我們大概知道要怎么做了!!
🍃1. Timer 內部要組織很多的任務;
🍃2. Timer 里的每個任務都要通過一定的方式來描述出來;(自己定義一個 TimerTask)
🍃3. 還需要有一個線程,通過這個線程來掃描定時器內部的任務,執行其中時間到了的任務。(Timer 內部的線程)
🍁【代碼實現】
class MyTask implements Comparable<MyTask>{// 任務private Runnable command;// 任務開始執行的時間(相對時間)private long time;public MyTask(Runnable command, long after) {this.command = command;// 絕對時間戳this.time = System.currentTimeMillis() + after;}// 執行任務的方法,直接在內部調用 Runnable 的 run 方法即可public void run() {command.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {return (int) (this.time - o.time);} } //自己創建的定時器類 class MyTimer {// 用來阻塞等待的鎖對象private Object locker = new Object();// 使用優先級阻塞隊列來保存若干個任務private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// command : 要執行的任務是啥// after : 任務啥時候執行public void schedule(Runnable command, long after) {MyTask myTask = new MyTask(command, after);synchronized (locker) {queue.put(myTask);locker.notify();}}public MyTimer() {//在這里啟動一個線程Thread t = new Thread(() -> {while(true) {//循環過程中,就不斷的嘗試從隊列中獲取到隊首元素//判斷隊首元素當前的時間是否就緒,如果就緒了就執行,不就緒,就不執行try {synchronized (locker) {//隊列為空,也要等待if(queue.isEmpty()) {locker.wait();}//取出隊首任務MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(myTask.getTime() > curTime) {//時間還沒到,就放回去queue.put(myTask);//等待最早的任務開始的時間和當前時間的差值locker.wait(myTask.getTime() - curTime);} else {//時間到了,就執行任務myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();} }【分析實現過程中的重點步驟】
🍁重點 1:使用優先級阻塞隊列將我們的任務組織起來!!
🍃1.雖然任務可能有很多,但是它們的執行順序是一定的,且按照時間順序先后來執行的,所以使用優先級隊列。
🍃2.在多線程環境下,這個隊列會被多個線程訪問,第一,schedule 可能是在多線程中被調用,每次調用都要往隊里添加元素;第二,定時器內部還需要有專門的線程來執行隊列里的任務。這些操作在多線程里都是存在線程安全問題的,所以需要使用到優先級阻塞隊列!!
🍁重點2:優先級阻塞隊列里的元素是一個引用類型(MyTask),所以我們需要指定比較規則,既可以讓 MyTask 實現 Comparable 接口,也可以在優先級阻塞隊列的構造方法中傳參,傳一個比較器 Comparator !!
🍁重點3:執行任務的時候:隊列不為空時,我們先將元素取出來,判斷是否到了執行時間,沒到時間就放回去,放回去之后要加上 wait() 。那么加入新任務的時候也就相應的要 notify() ,避免讓 CPU 出現"空轉"的現象!!
?
🍁重點4:我們的加鎖不能只包裹?wait() ,notify(),否則會出現非原子性操作,從而導致線程安全問題!!
?
經過上述分析,我們發現多線程的代碼真的是防不勝防,稍微一點不注意,都可能引起線程安全問題!!
總結
- 上一篇: mui 搜索框图标左对齐
- 下一篇: 【MySQL】求差集