數據結構和算法專題---6、定時算法與應用

本章我們會對定時算法做個簡單介紹,包括常用的定時算法(最小堆、時間輪)的概述、實現方式、典型場景做個說明。

概述

系統或者項目中難免會遇到各種需要自動去執行的任務,實現這些任務的手段也多種多樣,如操作系統的crontab,spring框架的quartz,java的Timer和ScheduledThreadPool都是定時任務中的典型手段。

最小堆

概念

Timer是java中最典型的基于優先級隊列+最小堆實現的定時器,內部維護一個存放定時任務的優先級隊列,該優先級隊列使用了最小堆排序。當我們調用schedule方法的時候,一個新的任務被加入queue,堆重排,始終保持堆頂是執行時間最小(即最近馬上要執行)的。同時,內部相當于起了一個線程不斷掃描隊列,從隊列中依次獲取堆頂元素執行,任務得到調度。

下面以Timer為例,介紹優先級隊列+最小堆算法的實現原理:

案例

package com.ls.cloud.sys.alg.Timer;import java.util.Timer;
import java.util.TimerTask;class Task extends TimerTask {@Overridepublic void run() {System.out.println("running...");}
}
public class TimerDemo {public static void main(String[] args) {Timer t=new Timer();//在1秒后執行,以后每2秒跑一次t.schedule(new Task(), 1000,2000);}
}

源碼分析

新加任務時,t.schedule方法會add到隊列

 void add(TimerTask task) {// Grow backing store if necessaryif (size + 1 == queue.length)queue = Arrays.copyOf(queue, 2*queue.length);queue[++size] = task;fixUp(size);}

add實現了容量維護,不足時擴容,同時將新任務追加到隊列隊尾,觸發堆排序,始終保持堆頂元素最小

 private void fixUp(int k) {while (k > 1) {//k指針指向當前新加入的節點,也就是隊列的末尾節點,j為其父節點int j = k >> 1;//如果新加入的執行時間比父節點晚,那不需要動if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)break;//如果大于其父節點,父子交換TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;//交換后,當前指針繼續指向新加入的節點,繼續循環,知道堆重排合格k = j;}}

線程調度中的run,主要調用內部mainLoop()方法,使用while循環

  private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// Wait for queue to become non-emptywhile (queue.isEmpty() && newTasksMayBeScheduled)queue.wait();if (queue.isEmpty())break; // Queue is empty and will forever remain; die// Queue nonempty; look at first evt and do the right thinglong currentTime, executionTime;task = queue.getMin();synchronized(task.lock) {if (task.state == TimerTask.CANCELLED) {queue.removeMin();continue;  // No action required, poll queue again}currentTime = System.currentTimeMillis();executionTime = task.nextExecutionTime;//判斷是否到了執行時間if (taskFired = (executionTime<=currentTime)) {//判斷下一次執行時間,單次的執行完移除                        //循環的修改下次執行時間if (task.period == 0) { // Non-repeating, removequeue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule//下次時間的計算有兩種策略                            //1.period是負數,那下一次的執行時間就是當前時間‐period                            //2.period是正數,那下一次就是該任務本次的執行時間+period                            //注意!這兩種策略大不相同。因為Timer是單線程的                            //如果是1,那么currentTime是當前時間,就受任務執行長短影響                            //如果是2,那么executionTime是絕對時間戳,與任務長短無關queue.rescheduleMin(task.period<0 ? currentTime   - task.period: executionTime + task.period);}}}//不到執行時間,等待if (!taskFired) // Task hasn't yet fired; waitqueue.wait(executionTime - currentTime);}//到達執行時間,run!if (taskFired)  // Task fired; run it, holding no lockstask.run();} catch(InterruptedException e) {}}}
}

應用

  • Timer是單線程,一旦一個失敗或出現異常,將打斷全部任務隊列,線程池不會
  • Timer在jdk1.3+,而線程池需要jdk1.5+

時間輪

概念

時間輪是一種更為常見的定時調度算法,各種操作系統的定時任務調度,linux crontab,基于java的通信框架Netty等。其靈感來源于我們生活中的時鐘。 輪盤實際上是一個頭尾相接的環狀數組,數組的個數即是插槽數,每個插槽中可以放置任務。 以1天為例,將任務的執行時間%12,根據得到的數值,放置在時間輪上,小時指針沿著輪盤掃描,掃到的點取出任務執行:

實現

package com.ls.cloud.sys.alg.Timer;public class RoundTask {//延遲多少秒后執行int delay;//加入的序列號,只是標記一下加入的順序int index;public RoundTask(int index, int delay) {this.index = index;this.delay = delay;}void run() {System.out.println("task " + index + " start , delay = "+delay);}@Overridepublic String toString() {return String.valueOf(delay);}
}
package com.ls.cloud.sys.alg.Timer;import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class RoundDemo {//小輪槽數int size1=10;//大輪槽數int size2=5;//小輪,數組,每個元素是一個鏈表LinkedList<RoundTask>[] t1 = new LinkedList[size1];//大輪LinkedList<RoundTask>[] t2 = new LinkedList[size2];//小輪計數器,指針跳動的格數,每秒加1final AtomicInteger flag1=new AtomicInteger(0);//大輪計數器,指針跳動個格數,即每10s加1final AtomicInteger flag2=new AtomicInteger(0);//調度器,拖動指針跳動ScheduledExecutorService service = Executors.newScheduledThreadPool(2);public RoundDemo(){//初始化時間輪for (int i = 0; i < size1; i++) {t1[i]=new LinkedList<>();}for (int i = 0; i < size2; i++) {t2[i]=new LinkedList<>();}}//打印時間輪的結構,數組+鏈表void print(){System.out.println("t1:");for (int i = 0; i < t1.length; i++) {System.out.println(t1[i]);}System.out.println("t2:");for (int i = 0; i < t2.length; i++) {System.out.println(t2[i]);}}//添加任務到時間輪void add(RoundTask task){int delay = task.delay;if (delay < size1){//10以內的,在小輪,槽取余數t1[delay%size1].addLast(task);}else {//超過小輪的放入大輪,槽除以小輪的長度t2[delay/size1].addLast(task);}}void startT1(){//每秒執行一次,推動時間輪旋轉,取到任務立馬執行service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {int point = flag1.getAndIncrement()%size1;System.out.println("t1 -----> slot "+point);LinkedList<RoundTask> list = t1[point];if (!list.isEmpty()){//如果當前槽內有任務,取出來,依次執行,執行完移除while (list.size() != 0){list.getFirst().run();list.removeFirst();}}}},0,1, TimeUnit.SECONDS);}void startT2(){//每10秒執行一次,推動時間輪旋轉,取到任務下方到t1service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {int point = flag2.getAndIncrement()%size2;System.out.println("t2 =====> slot "+point);LinkedList<RoundTask> list = t2[point];if (!list.isEmpty()){//如果當前槽內有任務,取出,放到定義的小輪while (list.size() != 0){RoundTask task = list.getFirst();//放入小輪哪個槽呢?小輪的槽按10取余數t1[task.delay % size1].addLast(task);//從大輪中移除list.removeFirst();}}}},0,10, TimeUnit.SECONDS);}public static void main(String[] args) {RoundDemo roundDemo = new RoundDemo();//生成100個任務,每個任務的延遲時間隨機for (int i = 0; i < 100; i++) {roundDemo.add(new RoundTask(i,new Random().nextInt(50)));}//打印,查看時間輪任務布局roundDemo.print();//啟動大輪roundDemo.startT2();//小輪啟動roundDemo.startT1();}}

結果

t1 -----> slot 1
task 8 start , delay = 11
task 45 start , delay = 11
task 87 start , delay = 11
t1 -----> slot 2
task 40 start , delay = 12
task 89 start , delay = 12
t1 -----> slot 3
task 25 start , delay = 13
t1 -----> slot 4
task 69 start , delay = 14
t1 -----> slot 5
  • 輸出結果嚴格按delay順序執行,而不管index是何時被提交的
  • t1為小輪,10個槽,每個1s,10s一輪回
  • t2為大輪,5個槽,每個10s,50s一輪回
  • t1循環到每個槽時,打印槽內的任務數據,如 t1–>slot9 , 打印了3個9s執行的數據
  • t2循環到每個槽時,將槽內的任務delay時間取余10后,放入對應的t1槽中,如 t2==>slot1
  • 那么t1旋轉對應的圈數后,可以取到t2下放過來的任務并執行,如10,11…

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

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

相關文章

【C++】使用“/**/“進行注釋的好處

2023年12月10日&#xff0c;周日晚上 我今天下午看Google Chrome的源碼時&#xff0c;才發現"/**/"原來還能這么用 使用"/**/"的好處就是&#xff0c;可以在任何地方進行注釋&#xff0c;哪怕是參數列表 void CircularWindow::enterEvent(QEvent *event/…

【Python】判斷域名是否合法

python判斷域名是否合法|校驗域名 域名以點號分隔成多個字符串。單個字符串由各國文字的特定字符集、字母、數字、連字符&#xff08;-&#xff09;組成&#xff0c;字母不區分大小寫&#xff0c;連字符&#xff08;-&#xff09;不得出現在字符串的頭部或者尾部。單個字符串長…

GitHub Enterprise Server 添加代碼安全、自動化功能

GitHub的軟件更新用于管理私有服務器上的存儲庫&#xff0c;具有GitHub容器注冊訪問、Dependabot安全警報和更新以及可重用工作流的特性。 GitHub Enterprise Server 3.5是GitHub用于托管和管理私有服務器上存儲庫的最新版本&#xff0c;它引入了新的代碼安全特性&#xff0c;新…

Helm 常用運維命令

原理參考 ## https://blog.csdn.net/knight_zhou/article/details/122079292 常用運維命令 helm search: ??搜索charthelm pull: ???下載chart到本地目錄查看helm install: ??上傳chart到Kuberneteshelm list: ????列出已發布的chart

【開源】基于Vue和SpringBoot的車險自助理賠系統

項目編號&#xff1a; S 018 &#xff0c;文末獲取源碼。 \color{red}{項目編號&#xff1a;S018&#xff0c;文末獲取源碼。} 項目編號&#xff1a;S018&#xff0c;文末獲取源碼。 目錄 一、摘要1.1 項目介紹1.2 項目錄屏 二、功能模塊2.1 數據中心模塊2.2 角色管理模塊2.3 車…

Maven基礎

目錄 Maven坐標 坐標簡介 主要組成 Maven依賴管理 配置依賴 依賴簡介 配置依賴 依賴傳遞 依賴傳遞簡介 排除依賴 依賴范圍 生命周期 生命周期簡介 執行指定生命周期 Maven坐標 坐標簡介 Maven中的坐標是資源的唯一標識&#xff0c;通過該坐標可以唯一定位資…

Redis交互速度慢,CPU占用100%,集群方案,報錯等問題

后續補充結論 仔細查看前輩們堆的代碼中發現居然調用了大量key*查詢&#xff0c;導致走的遍歷非常慢&#xff01;因為這相當與全部數據量遍歷&#xff0c;即這個原因導致了查詢速度與數據量成正比&#xff0c;推測也是CPU占用高的元兇&#xff1b;即使加上key前綴再匹配*也會走…

Python開發運維:Python調用K8S API實現資源管理

目錄 一、實驗 1.Python操作K8S API獲取資源 2.Python操作K8S API創建deployment資源 3.Python操作K8S API刪除k8s資源 4.Python操作K8S API修改k8s資源 5.Python操作K8S API查看k8s資源 二、問題 1.Windows11安裝kubernetes報錯 2.Python通過調用哪些方法實現Pod和De…

在SpringData JPA 中實現對持久層的操作

1.導入依賴 hibernate 這個依賴自帶實現JPA接口 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><depen…

TCP三次握手、四次揮手及狀態轉換詳解

1.什么是TCP協議&#xff1f; 傳輸控制協議&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一種面向連接的、可靠的、基于字節流的傳輸層通信協議&#xff0c;位于網絡OSI七層模型的第四層&#xff0c;IP協議一起工作&#xff0c;TCP層是位于IP層之上…

(Spring學習07)Spring之啟動刷新過程源碼解析

概述 通常&#xff0c;我們說的Spring啟動&#xff0c;就是構造ApplicationContext對象以及調用refresh()方法的過程。 首先&#xff0c;Spring啟動過程主要做了這么幾件事情&#xff1a; 構造一個BeanFactory對象解析配置類&#xff0c;得到BeanDefinition&#xff0c;并注冊…

CrystalDiskInfo中文版(硬盤檢測工具) v9.1.1.0 綠色漢化版-供大家學習研究參考

更新內容 重新支持三星SATA SSD壽命報告 增加對ZHITAI SC001的支持 新增SK hynix Gold S31支持 增加了KLEVV NEO N610的支持。 改進的Micron/Crucial SATA SSD支持 已更改 卸載程序將顯示一個確認對話框&#xff0c;用于刪除設置。 強大功能 1.擁有多國語言&#xff0c;…

27 動態規劃解最大子序和

問題描述&#xff1a;給定一個整數數組nums&#xff0c;找到一個具有最大和的連續子數組(子數組最少含有一個元素)&#xff0c;返回其最大和。 動態規劃求解&#xff1a;定義dp[i]表示以i元素為結尾的最大和&#xff0c;如果dp[i-1]小于零的話&#xff0c;dp[i]nums[i],否則dp…

React-hook-form-mui(三):表單驗證

前言 在上一篇文章中&#xff0c;我們介紹了react-hook-form-mui的基礎用法。本文將著重講解表單驗證功能。 react-hook-form-mui提供了豐富的表單驗證功能&#xff0c;可以通過validation屬性來設置表單驗證規則。本文將詳細介紹validation的三種實現方法&#xff0c;以及如何…

ts中type和interface類型聲明的區別

1. 寫法上 type 使用關鍵字 type 進行聲明。 interface 使用關鍵字 interface 進行聲明。 // 使用 type type MyType {param: string; };// 使用 interface interface MyInterface {param: string; }2. 可合并性 interface 具有可合并性&#xff0c;允許在同一作用域內多次…

045:Vue讀取本地上傳JSON文件,導出JSON文件方法

第045個 查看專欄目錄: VUE ------ element UI 專欄目標 在vue和element UI聯合技術棧的操控下&#xff0c;本專欄提供行之有效的源代碼示例和信息點介紹&#xff0c;做到靈活運用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安裝、引用&#xff0c;模板使…

jquery手寫廣告輪播圖,無限循環功能

說明 在很多情況下&#xff0c;我們都需要開發廣告輪播圖&#xff0c;當我們進行頁面的功能開發時&#xff0c;采用輪播圖來實現也行&#xff0c;但是很多情況下&#xff0c;我們只需要簡單的控制輪播循環輪播展示即可&#xff0c;所以用jq開開發廣告輪播波&#xff0c;自定義…

spring更加松散的獲取bean的方式ObjectProvider

概述 ObjectProvider直譯就是對象提供者; 平時從spring中獲取bean都是調用beanFactory.getBean()方法&#xff0c;如果bean不存在則會直接拋異常; 從spring 4.3開始引入了org.springframework.beans.factory.ObjectProvider接口,其提供了若干的方法&#xff0c;可以更松散的…

Idea 插件開發: Swing Designer設計器創建的組件全部為空問題記錄

問題現象 通過Swing 設計器創建的對象, Swing組件全部是空的, 導致ToolWindowFactory工廠的實現類調用時候出現了空指針異常 如下方式創建的 問題分析 問題出現時候, 同時給我生成了一個createUIComponents的私有方法, 由于個人當時理解有誤, 把他當成了初始化方法, 在里面…

Oracle高可用一家老小全在這里

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈嘍&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人稱jeames007&#xff0c;10余年DBA及大數據工作經驗 一位上進心十足的【大數據領域博主】&#xff01;&#x1f61c;&am…