什么是定時器?

??前言👀~

上一章我們介紹了阻塞隊列以及生產者消息模式,今天我們來講講定時器

定時器

標準庫中的定時器

schedule()方法

掃描線程

手動實現定時器

任務類

存儲任務的數據結構

定時器類


如果各位對文章的內容感興趣的話,請點點小贊,關注一手不迷路,講解的內容我會搭配我的理解用我自己的話去解釋如果有什么問題的話,歡迎各位評論糾正 🤞🤞🤞

12b46cd836b7495695ce3560ea45749c.jpeg

個人主頁:N_0050-CSDN博客

相關專欄:java SE_N_0050的博客-CSDN博客??java數據結構_N_0050的博客-CSDN博客??java EE_N_0050的博客-CSDN博客


定時器

定時器是個非常常見的組件,尤其是在網絡進行通信的時候,類似發郵件,類似于一個 "鬧鐘",達到一個設定的時間之后, 就執行某個指定好的代碼

舉個例子,當客戶端給服務器發送請求后,服務器半天沒有響應,就像你發郵件一樣,發的時候會轉圈圈,成功了就會顯示發送成功或者什么提示信息,如果服務器沒有響應,你這邊可能就一直在那轉圈圈。我們也不知道是什么原因造成的,可能是請求沒發過去,可能是響應丟了,也可能是服務器出現了問題。所以對于客戶端來說,也可以說對用戶來說,肯定不能一直等啊那體驗多不好啊,所以設置一個等待時間(最大的期限),過了這個等待時間把電腦砸了,開個玩笑,過了這個最大期限,我們選擇重新發一遍,或者直接不發,或者重開這個程序等等方式。這里的最大期限我們可以使用定時器去實現


標準庫中的定時器

首先我們先使用一下定時器Timer類,再去講解,代碼如下

public class Test1 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("啟動成功");}}, 1000);System.out.println("原神啟動");}
}

輸出結果


schedule()方法

這個方法涉及兩個參數 第一個參數描述了任務要做什么這里使用匿名內部類去創建一個TimerTask實例第二個參數就是時間就是要在多長時間(單位為毫秒)后去執行,這個時間是根據當前時間為準然后根據你設定的時間來執行任務的,比如說現在11:00:00你設置1秒后執行就是11:00:01執行任務。然后前面用匿名內部類創建出來的TimerTask實例實現了Runnable接口,然后我們重寫方法定義自己要執行的任務通過schedule方法,接著再由掃描線程去執行


掃描線程

當我們創建出這個timer對象后,這個線程也就被創建出來了,后續要執行任務,都是通過這個線程去執行的

來看剛才這段代碼以及輸出結果

public class Test1 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("啟動成功");}}, 1000);System.out.println("原神啟動");}
}

輸出結果,你會發現整個進程并沒有結束,主線程執行schedule方法的時候,是把這個任務丟給timer對象中的一個線程去處理的,這個線程可以叫"掃描線程",你設置的時間一到,就去掃描任務也就是執行你寫的任務。

解釋:為什么整個進程沒有結束?timer中的這個線程阻止了進程結束,它在等我們再給它安排任務,相當于服務員,你有什么吩咐它就執行,沒有任務就在那等并且timer里可以安排多個任務


手動實現定時器

根據上面標準庫可以得出以下要求:

1.和上面標準庫提供的timer類一樣,我們需要一個掃描線程,然后去執行任務

2.需要一個數據結構,把所有要執行的任務保存起來

3.需要使用一個類,通過一個類的對象去來描述執行的任務(任務內容和執行時間)


任務類

首先寫一個用來描述任務的類,包含任務內容和執行時間

在設置任務執行時間的時候,有兩種方式,一種是相對的時間,一種是絕對的時間(完整的時間戳),兩種都可以這里我們選擇絕對時間,因為相對時間要計算間隔后的時間然后進行比較,絕對時間獲取當前時間戳加上任務執行時間然后進行比較。

下面是任務類的實現,不只這一種,最后完整代碼有兩種

//用來描述任務的類 包含任務的內容和執行時間
class MyTimerTask {private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;//使用絕對時機 時間戳+傳入的時間}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}
}


存儲任務的數據結構

這里的數據結構我們采用優先級隊列去保存需要執行的任務,因為我們肯定要先執行時間最少的任務,然后優先級隊列也就是堆,最頂層的就是最小的,并且優先級隊列取出元素(也就是獲取時間最少的任務)時間復雜度都為O(1)

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

但是注意優先隊列要求放入的元素是可以比較的,也就是我們的任務之間可以進行比較,所以我們還需要實現自定義比較器,使用時間進行比較除了優先級隊列中的元素需要能進行比較的,還有二叉搜索樹也就是TreeMap和TreeSet

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});

定時器類

我們的定時器和標準庫中的定時器一樣,我們需要一個掃描線程執行任務,還需要一個schedule方法,上面的優先級隊列也放在定時器中,下面是代碼實現,需要注意線程不安全問題,會出現這樣的可能就比如主線程在向隊列添加元素的時候,掃描線程也在對隊列進行判斷,導致加入了元素的時候這里正好進行判斷,然后為空進入阻塞狀態

class MyTimer {//優先級隊列存儲任務 優先級隊列的元素要能進行比較 所以要實現比較器 我們根據時間進行比較public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {//創建出定時器對象的時候 啟動掃描線程thread.start();}//給用戶調用的方法 傳入要完成的任務以及時間public void schedule(Runnable runnable, long delay) {synchronized (lock) {//避免線程不安全問題 有任務了就喚醒線程進行工作if (delay < 0) {throw new IllegalArgumentException("輸入的時間有誤");} else {queue.offer(new MyTimerTask(runnable, delay));//調用這個方法的時候 創建任務然后放進隊列進行處理lock.notify();}}}public Object lock = new Object();public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使沒任務 也等我們給它分配任務while (queue.isEmpty()) {//隊列為空進入阻塞 使用while保險起見try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//記錄當前時間MyTimerTask task = queue.peek();//先看任務的時間 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//獲取到引用去執行用戶的任務} else {}}}});
}

還有一個地方需要進行優化比如就是你設置執行任務的時間在10點半,然后else那塊不寫代碼,它會一直到while循環開始判斷一路下路,一直到時間到去執行任務,這樣做消耗太多cpu資源,解決辦法,讓線程在這里休息,使用帶參數的wait方法,當前執行任務時間減去當前時間作為參數

    public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使沒任務 也等我們給它分配任務while (queue.isEmpty()) {//隊列為空進入阻塞 使用while保險起見try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//記錄當前時間MyTimerTask task = queue.peek();//先看任務的時間 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//獲取到引用去執行用戶的任務} else {}}}});

兩種完整代碼

第一種任務類是沒有直接實現Runnable接口

//用來描述任務的類 包含任務的內容和執行時間
class MyTimerTask {private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;//使用絕對時機 時間戳+傳入的時間}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}
}//定時器
class MyTimer {//優先級隊列存儲任務 優先級隊列的元素要能進行比較 所以要實現比較器 我們根據時間進行比較public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {//創建出定時器對象的時候 啟動掃描線程thread.start();}//給用戶調用的方法 傳入要完成的任務以及時間public void schedule(Runnable runnable, long delay) {synchronized (lock) {//避免線程不安全問題 有任務了就喚醒線程進行工作if (delay < 0) {throw new IllegalArgumentException("輸入的時間有誤");} else {queue.offer(new MyTimerTask(runnable, delay));//調用這個方法的時候 創建任務然后放進隊列進行處理lock.notify();}}}public Object lock = new Object();public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使沒任務 也等我們給它分配任務while (queue.isEmpty()) {//隊列為空進入阻塞 使用while保險起見try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//記錄當前時間MyTimerTask task = queue.peek();//先看任務的時間 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//獲取到引用去執行用戶的任務} else {}}}});
}

第二種是任務類實現Runnable接口

//用來描述任務的類 就是存儲任務的內容以及執行時間
class MyTimerTask implements Runnable {private long time;private Runnable task;public MyTimerTask(Runnable runnable, long delay) {this.task = runnable;this.time = System.currentTimeMillis() + delay;//使用絕對時間 當前時間戳+多少秒后執行=執行時間}public long getTime() {return time;}@Overridepublic void run() {//外層的這個就是一個殼,通過調用這個方法執行里面我們自己寫的任務task.run();// 這個就是我們自己寫的任務}
}//定時器 包含存儲隊列 掃描線程 創建任務
class MyTimer {public Object lock = new Object();//使用優先級隊列存儲任務 因為取出任務的時間復雜度為0(1) 注意要比較器 因為我們要使用時間比較出誰是最小的public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());//return Long.compare(o1.getTime(), o2.getTime());//可以避免溢出}});//初始化定時器就啟動掃描線程public MyTimer() {thread.start();}//把任務和執行時間傳到這方法 然后通過這個方法創建任務類去裝任務和時間public void schedule(Runnable runnable, long delay) {synchronized (lock) {if (delay < 0) {throw new IllegalArgumentException("輸入的時間有誤!!!");} else {queue.offer(new MyTimerTask(runnable, delay));lock.notify();}}}//創建掃描線程執行任務public Thread thread = new Thread(() -> {//因為掃描線程會一直掃描任務 它在等我們再給它安排任務while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}//不是直接poll 任務執行的時候要和當前時間進行比較后 再進行poll去執行//這個拿的任務相當于我們自己寫的任務MyTimerTask task = queue.peek();long currentTime = System.currentTimeMillis();//如果當前時間等于或者超過任務的執行時間就執行任務if (currentTime >= task.getTime()) {task.run();//queue.poll();} else {try {//讓線程休息到執行任務的時間lock.wait(task.getTime() - currentTime);} catch (InterruptedException e) {e.printStackTrace();}}}}});
}

以上便是本章內容,定時器在日常開發中還是會用到的,例如發郵件這類的,所以還是需要好好掌握,我們下一章再見💕

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38604.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38604.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38604.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Python】列表

目錄 一、列表的概念 二、列表的創建 1.變量名 [ ] ..... 2.通過Python內置 的I ist類的構造函數來創建列表 三、操作列表元素的方法 1. 修改 2. 增加元素 3. 刪除 4. 其他操作 四、遍歷列表 五、列表排序 六、列表切片&#xff08;list slicing&#xff09; 七、…

淺談什么是計算機科學與技術(Computer Science,CS)

計算機科學的核心內容 計算機科學&#xff08;Computer Science, CS&#xff09;涵蓋了以下主要領域&#xff1a; 硬件&#xff1a;涉及數字電路、集成電路、存儲器和硬件設計與驗證方法等。 例子&#xff1a;學習如何設計和實現一個簡單的CPU&#xff0c;包括理解指令集、時鐘…

值得細讀的8個視覺大模型生成式預訓練方法

作者&#xff1a;vasgaowei&#xff08;已授權原創&#xff09; 編輯: AI生成未來 鏈接&#xff1a;https://zhuanlan.zhihu.com/p/677794719 大語言模型的進展催生出了ChatGPT這樣的應用&#xff0c;讓大家對“第四次工業革命”和“AGI”的來臨有了一些期待&#xff0c;也作為…

Linux基礎指令介紹與詳解——原理學習

前言&#xff1a;本節內容標題雖然為指令&#xff0c;但是并不只是講指令&#xff0c; 更多的是和指令相關的一些原理性的東西。 如果友友只想要查一查某個指令的用法&#xff0c; 很抱歉&#xff0c; 本節不是那種帶有字典性質的文章。但是如果友友是想要來學習的&#xff0c;…

[ALSA]從零開始,使用ALSA驅動播放一個音頻

前言 最近學了不少有關音頻相關的&#xff0c;最近搞一下ALSA驅動 安裝 參考Linux應用開發【第八章】ALSA應用開發 中提到的ALSA庫及工具章節&#xff0c;本文中有比較詳細的有關ALSA驅動引用程序怎么安裝的&#xff0c;這里不再贅述。 關于ALSA&#xff0c;就當成一個音頻…

深入淺出:npm常用命令詳解與實踐【保姆級教程】

大家好,我是CodeQi! 在我剛開始學習前端開發的時候,有一件事情讓我特別頭疼:管理和安裝各種各樣的依賴包。 那時候,我還不知道 npm 的存在,手動下載和管理這些庫簡直是噩夢。 后來,我終于接觸到了 npm(Node Package Manager),它不僅幫我解決了依賴管理問題,還讓我…

Python深度理解系列之【排序算法——冒泡排序】

讀者大大們好呀&#xff01;&#xff01;!?????? &#x1f440;期待大大的關注哦?????? &#x1f680;歡迎收看我的主頁文章??木道尋的主頁 文章目錄 &#x1f525;前言&#x1f680;冒泡排序python實現算法實現圖形化算法展示 ??????總結 &#x1f525;前…

Apache POI、EasyPoi、EasyExcel

目錄 ?編輯 &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 寫 讀 最簡單的讀? 最簡單的讀的excel示例? 最簡單的讀的對象? &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&…

golang go-bindata打包配置文件嵌入到二進制文件

go-bindata打包配置文件嵌入到二進制文件 項目中難免會用到一些靜態資源和配置文件&#xff0c;但是常規打包的二進制文件無法再其他目錄正常運行&#xff08;靜態資源和配置文件不存在&#xff09; 有類似需求的可以安裝使用&#xff1a;go-bindata進行編譯處理配置文件 go-bi…

train_encoder_decoder.py

train_encoder_decoder.py from __future__ import print_function #為了確保代碼同時兼容Python 2和Python 3版本中的print函數# 導入標準庫和第三方庫 import os.path #導入了Python的os.path模塊&#xff0c;用于處理文件和目錄路徑 from os import path #從os模塊中導入了…

【場景題】數據庫優化和接口優化——異步思想

理解 異步處理&#xff1a; 對于耗時的操作&#xff0c;可以考慮使用異步處理方式來提升接口的響應速度。用戶可以在不阻塞當前操作的情況下&#xff0c;等待異步操作的結果。 異步處理在數據庫優化中的應用 雖然數據庫操作本身&#xff08;如查詢、插入、更新等&#xff09…

Git 安裝

目錄 Git 安裝 Git 安裝 在使用 Git 前我們需要先安裝 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平臺上運行。Git 各平臺安裝包下載地址為&#xff1a;http://git-scm.com/downloads 在 Linux 平臺上安裝&#xff08;包管理工具安裝&#xff09; 首先&#xff0…

IIS在Windows上的搭建

&#x1f4d1;打牌 &#xff1a; da pai ge的個人主頁 &#x1f324;?個人專欄 &#xff1a; da pai ge的博客專欄 ??寶劍鋒從磨礪出&#xff0c;梅花香自苦寒來 目錄 一 概念&#xff1a; 二網絡…

深入理解C++中的鎖

目錄 1.基本互斥鎖&#xff08;std::mutex&#xff09; 2.遞歸互斥鎖&#xff08;std::recursive_mutex&#xff09; 3.帶超時機制的互斥鎖&#xff08;std::timed_mutex&#xff09; 4.帶超時機制的遞歸互斥鎖&#xff08;std::recursive_timed_mutex&#xff09; 5.共享…

【python腳本】批量檢測sql延時注入

文章目錄 前言批量檢測sql延時注入工作原理腳本演示 前言 SQL延時注入是一種在Web應用程序中利用SQL注入漏洞的技術&#xff0c;當傳統的基于錯誤信息或數據回顯的注入方法不可行時&#xff0c;例如當Web應用進行了安全配置&#xff0c;不顯示任何錯誤信息或敏感數據時&#x…

【TS】TypeScript 原始數據類型深度解析

&#x1f308;個人主頁: 鑫寶Code &#x1f525;熱門專欄: 閑話雜談&#xff5c; 炫酷HTML | JavaScript基礎 ?&#x1f4ab;個人格言: "如無必要&#xff0c;勿增實體" 文章目錄 TypeScript 原始數據類型深度解析一、引言二、基礎原始數據類型2.1 boolean2.2 …

蒼穹外賣--sky-take-out(四)10-12

蒼穹外賣--sky-take-out&#xff08;一&#xff09; 蒼穹外賣--sky-take-out&#xff08;一&#xff09;-CSDN博客?編輯https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2…

Unity動畫系統(2)

6.1 動畫系統基礎2-3_嗶哩嗶哩_bilibili p316 模型添加Animator組件 動畫控制器 AnimatorController AnimatorController 可以通過代碼控制動畫速度 建立動畫間的聯系 bool值的設定 trigger p318 trigger點擊的時候觸發&#xff0c;如喊叫&#xff0c;開槍及換子彈等&#x…

在js中如何Json字符串格式不對,如何處理

如果 JSON 字符串格式不正確&#xff0c;解析它時會拋出異常&#xff0c;但我們可以嘗試盡可能提取有效的信息。以下是一個方法&#xff0c;可以使用正則表達式和字符串操作來提取部分有效的 JSON 內容&#xff0c;即使整個字符串無法被 JSON.parse 完全解析。 示例代碼如下&a…

錯誤 [WinError 10013] 以一種訪問權限不允許的方式做了一個訪問套接字的嘗試 python ping

報錯提示&#xff1a;錯誤 [WinError 10013] 以一種訪問權限不允許的方式做了一個訪問套接字的嘗試 用python做了一個批量ping腳本&#xff0c;在windows專業版上沒問題&#xff0c;但是到了windows服務器就出現這個報錯 解決方法&#xff1a;右鍵 管理員身份運行 這個腳本 …