java任務調度之Timer定時器(案例和源碼分析)

2020-12-05 愚公要移山1

定時器相信大家都不陌生,平時使用定時器就像使用鬧鐘一樣,我們可以在固定的時間做某件事,也可以在固定的時間段重複做某件事,今天就來分析一下java中自帶的定時任務器Timer。

一、Timer基本使用

在Java中為我們提供了Timer來實現定時任務,當然現在還有很多定時任務框架,比如說Spring、QuartZ、Linux Cron等等,而且性能也更加優越。但是我們想要深入的學習就必須先從最簡單的開始。

在Timer定時任務中,最主要涉及到了兩個類:Timer和TimerTask。他們倆的關係也特別容易理解,TimerTask把我們得業務邏輯寫好之後,然後使用Timer定時執行就OK了。我們來看一個最基本的案例:

這就是我們的TimerTask,我們單獨寫成類時候需要去繼承TimerTask。然後呢我們寫好了之後就可以使用Timer來執行了。

指定的流程很簡單:

(1)第一步:創建一個Timer。

(2)第二步:創建一個TimerTask。

(3)第三步:使用Timer執行TimerTask。

其中第三步無疑是我們目前最關心的,也就是timer.schedule(myTask, 2000L, 1000L)。他的意思是myTask在兩秒鐘之後開始第一次執行,然後每隔一秒執行一次。這只是最基本的用法。就體現了Timer定時執行的流程。當然java中Timer還為我們提供了很多其他的方法。對此就有必要深入其源碼看看了。

二、Timer源碼分析

對於一個類的源碼分析,我一貫的思路就是先從參數開始,然後構造方法,最後就是常用方法。下面我們就按照這個思路開始今天的源碼分析,在這裡基於jdk1.8。先給出一張整體類圖:

1、參數

Timer的源碼中為我們提供了兩個最主要的參數TaskQueue和TimerThread。

上面的代碼大概意思是這樣的:

(1)TaskQueue:這是一個最小堆,它存放該Timer的所有TimerTask。

(2)TimerThread:執行TaskQueue中的任務,執行完從任務隊列中移除。

所以上面這兩個參數其實是配合著使用的,那這個TaskQueue是如何存放的呢?在這裡我們不妨跟進去看看。

在這裡我們只給出了一部分源碼,不過這一部分是整個思想原理最核心的,上面英文的大概意思是;TaskQueue是一個平衡二叉堆,具有最小 nextExecutionTime 的 TimerTask 在隊列中為 queue[1] ,也就是堆中的根節點。第 n 個位置 queue[n] 的子節點分別在 queue[2n] 和 queue[2n+1] 。不了解二叉堆的話,可以看看數據結構。

也就是說TimerTask 在堆中的位置其實是通過nextExecutionTime 來決定的。nextExecutionTime 越小,那麼在堆中的位置越靠近根,越有可能先被執行。而nextExecutionTime意思就是下一次執行開始的時間。

還有一個TimerTask數組,默認大小是128個。

2、構造方法

構造方法就比較簡單了,這裡一共有四個:

(1)第一個:默認構造方法。

(2)第二個:在構造器中指定是否是守護線程。

(3)第三個:帶有名字的構造方法。

(3)第四個:不僅帶名字,還指定是否是守護線程。

不過我們需要注意一點的是,Timer在構造完成之後會啟動一個後臺線程用於執行TaskQueue裡面的TimerTask 。

3、定時任務方法

在一開始我們提到,我們不僅可以在指定的時間執行某些任務,還可以在一段時間之後執行。我們對這些方法進行總結一下:

(1)schedule(task,time) 在時間等於或超過time的時候執行且只執行一次task,這個time表示的是例如2019年11月11日上午11點11分11秒。指的是時刻。

(2)schedule(task,time,period)

在時間等於或超過time的時候首次執行task,之後每隔period毫秒重複執行一次task 。這個time和上一個一樣。

(3)schedule(task, delay)

在delay時間之後,執行且只執行一次task。這個delay表示的是延遲時間,比如說三秒後執行。

(4)schedule(task,delay,period)

在delay時間之後,開始首次執行task,之後每隔period毫秒重複執行一次task ,這個delay和上面的一樣。

我們不如來看看源碼:

這四個方法都執行了同一個方法sched,所以我們要弄清楚原理,就必須要再跟進去看看:

上面的代碼我們來分析一下,最上面的if就是排除一下異常情況,最核心的就是synchronized裡面的代碼。首先將任務添加到隊列中,然後根據nextExecutionTime調整隊列。

添加任務add(task):

維護最小堆:

上面就是Timer中如何執行的定時任務核心,但是還有一個方法,也是執行定時任務的。叫scheduleAtFixedRate

下面我們來分析一下,然後比較和上面的不同。

4、scheduleAtFixedRate方法

這個方法有兩個:

(1)scheduleAtFixedRate(task, time, period)

在時間等於或超過time的時候首次執行task,之後每隔period毫秒重複執行一次task 。這個time表示的是例如2019年11月11日上午11點11分11秒。指的是時刻。

(2)scheduleAtFixedRate(task, delay, period)

在delay時間之後,開始首次執行task,之後每隔period毫秒重複執行一次task ,這個delay表示的是延遲時間,比如說三秒後執行。

既然上面都已經有了4個定時器,為什麼這裡還要再增加幾個呢?我們來分析一下他們的區別:

分兩種情況: ① 首次計劃執行的時間 schedule:如果第一次執行時間被delay了,隨後的執行時間按照上一次實際執行完的時間點進行計算 。scheduleAtFixedRate:如果第一次執行時間被delay了,隨後的執行時間按上一次開始的時間進行計算,並且為了趕上進度會多次執行任務,因此TimerTask中的執行體需要考慮同步。

②任務執行所需時間 schedule方法:下一次執行時間會不斷延後,因此參照的是上一次執行完成的時間點。scheduleAtFixedRate方法:下一次執行時間不會延後,因此存在並發性。我們可以看一下圖:

5、其他方法

我們已經明白了如何創建Timer和執行定時任務,如果在執行的時候我們突然改變主意,想要取消怎麼辦呢?這裡Timer當然為我們提供了。

(1)cancel:取消此計時器任務。

(2)scheduledExecutionTime():返回此任務最近實際執行的安排執行時間。

6、任務調度

任務調度也就是說我們的線程如何去執行這些任務。其實在TimerThread調用了run來執行,我們看一下源碼。

也就是說其實真正執行任務調度的是mainLoop(),synchronized代碼塊只是為了確保在執行完之後能夠移除這個task。

而這個mainLoop方法的思想很簡單,就是拿出任務隊列中的第一個任務,如果執行時間還沒有到,則繼續等待,否則立即執行。源碼在這裡就不再給出了。

三、Timer缺陷

上面從源碼的角度分析了一下Timer,因為用法很簡單,主要是源碼分析。說了這麼多,Timer還是有一定的缺陷的,

1、Timer管理延時任務的缺陷

Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷。我們看一個例子:

這個例子中的功能是這樣的,第一個任務在1秒鐘之後開始執行,第二個任務在2秒鐘之後開始執行。

第一步:定義兩個TimerTask

還有一個:

第二步:我們測試一下:

我們在上面的Task1中會發現,任務2不是應該在32秒的時候執行嘛,怎麼會在4秒鐘之後才執行。究其原因是任務1執行了3秒,但是線程只有一個,所以只能先把任務1執行完才去執行任務2。這就是其缺陷之一。

2、Timer當任務拋出異常時的缺陷

這個缺陷的意思是,其中有一個任務拋出了RuntimeException,那麼所有的任務都會停止執行。這個演示起來很簡單。

第一步:聲明幾個定時任務

第二步:測試

我們來看一下結果:

正是Timer有很多的缺陷,所以出現了Timer的替代品ScheduledExecutorService,用來解決上面出現的問題。而且也出現了很多優秀的框架。具體的我會在後續文章中介紹。

OK,今天的文章到這,歡迎批評指正。

相關焦點

  • C#如何使用定時器[Timer]
    在新程序界面空白窗口上放置合適的控制項:包括,顯示操作流程的textbox控制項;用於時間間隔秒數的輸入框;開始計時器按鈕和停止計時器的按鈕;timer控制項。添加變量設置標誌位, 如果為0 停止計時, 如果為1 則開始計時;設置臨時變量;設置變量,獲取指定的間隔秒數。添加按鈕【開始】代碼獲取指定的間隔秒數;設置臨時變量;設置標誌位為開始。
  • Java API + Python AI,實現跨平臺任務調度
    六 業務處理服務集成Celery任務調度為什麼要跨平臺呢?Celery是一個靈活可靠的分布式系統,用於異步任務調度,系統通常將一些耗時的操作任務提交給Celery去異步執行,典型系統架構示意圖如下:本文基於Java + Spring Boot,Python + Django,集成ActiveMQ和Celery,搭建起一個跨平臺異步任務調度系統。
  • Envoy源碼分析之Dispatcher
    Dispatcher在Envoy的代碼中Dispatcher是隨處可見的,可以說在Envoy中有著舉足輕重的地位,一個Dispatcher就是一個EventLoop,其承擔了任務隊列、網絡事件處理、定時器、信號處理等核心功能。
  • AVR——使用定時器必須弄清的幾個概念
    定時器是獨立運行的,它不佔用CPU的時間,不需要指令,只有調用對應的寄存器的時候才需要參與。以AVR mega16為例,它有三個寄存器,timer0,timer1和timer2,T0和T2是8位定時器,T1是16位寄存器,T2為異步定時器,三個定時器都可以用於產生PWM。
  • 你知道java反射機制中class.forName和classloader的區別嗎?
    我們寫好的程序,然後run運行,過程可以直接看下面這張圖:往細了看大致分為5個階段:(1)加載:java類運行時候會生成一個class字節碼文件,加載的過程就是去我們的作業系統尋找這個class文件。(2)連結:這個過程就是把class文件加載到java虛擬機。
  • LPC1114_Timer16_0中斷程序_MDK編譯環境
    關於Timer16_0代碼: LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); //開啟定時器時鐘 LPC_TMR16B0->PR=1000;//1000-1;
  • 零基礎java入門教程函數function實例化格式案例void返回值說明
    java基礎自學入門:函數:定義在類中的具有特定功能的一段獨立小程序有時候我們函數也稱為方法,平時我們聽到的函數也就是方法,方法也是函數,每個人的叫法不同,所以這裡要切記。老程式設計師別露餡喲。將這個部分定義成一個獨立的功能,方便與日後使用java中對功能的定義是通過行數的形式來體現的,需要定義功能,完成一個整數*3+5的運算並列印結果
  • smart-doc 1.9.7 發布,Java 零註解文檔生成工具
    smart-doc是一款同時支持java restful api和apache dubbo rpc接口文檔生成的工具
  • 俯瞰Dubbo全局,閱讀源碼前必須掌握這些!!
    作者個人研發的在高並發場景下,提供的簡單、穩定、可擴展的延遲消息隊列框架,具有精準的定時任務和延遲隊列處理功能。自開源半年多以來,已成功為十幾家中小型企業提供了精準定時調度方案,經受住了生產環境的考驗。
  • 用經典案例來幫助初學者解析Java的「多態」
    我這裡還是用我13年前給我們公司新員工做內部培訓時用到的看起來似乎有點老掉牙的、但是仍然十分經典的案例來重新給有需要的java愛好者呈現一下「多態」的奧秘所在!這裡理論上的東西咱就先往後放一放,咱們先看看案例中的具體代碼、品一品、悟一悟、回味回味,可能就已經透徹了很多!
  • 深入分析java中的多態(從jvm角度分析)
    對於java中多態概念的理解一直是面試常問的問題,所以今天花了一些時間好好地整理了一下,力求從java虛擬機的角度來分析和理解多態。一、認識多態1、方法調用在Java中,方法調用有兩類,動態方法調用與靜態方法調用。
  • SpaceX-API源碼剖析(一)- 介紹
    01-BackgroundSpaceX-API開源已久,  一度想要好好寫一篇關於它的源碼剖析文章, 可拖延症當頭, 一直未能動筆.
  • MSP430 定時器輸出PWM波形
    MSP430F16x和MSP430F14x單片機內部均含有兩個定時器,TA和TB;TA有三個模塊,CCR0-CCR2;TB含有CCR0-CCR67個模塊;其中CCR0模塊不能完整的輸出PWM波形(只有三種輸出模式可用);TA可以輸出完整的2路PWM波形;TB可以輸出6路完整的PWM波形。
  • 超聲波測距原理(帶原理圖及源碼)
    電氣方式包括壓電型、磁致伸縮型和電動型等;機械方式有加爾統笛、液哨和氣流旋笛等。它們所產生的超聲波的頻率、功率和聲波特性各不相同,因而用途也各不相同。目前較為常用的是壓電式超聲波發生器。壓電式超聲波發生方式實際上是利用壓電晶體的諧振來工作的。
  • 單片機原理|定時器/計數器的工作原理及應用作業
    A、定時器T0採用方式1,定時5ms,每200次中斷後實現1s到B、定時器T0採用方式2,定時100us,每1000次中斷後實現1s到C、定時器T0採用方式0,定時10ms,每1000次中斷後實現1s到D、定時器T0採用方式3,定時100us,每1000次中斷後實現1s到我的答案:A
  • TIMER:腫瘤浸潤免疫細胞分析的綜合網站
    TIMER是一款腫瘤浸潤免疫細胞組分分析軟體,輸入腫瘤樣本的基因表達譜數據,預測每個腫瘤樣本中浸潤的免疫細胞組成,支持以下6種免疫細胞的分析B cellCD8
  • Java基礎之多態,動態綁定多態的代碼案例,簡單卻很重要
    需要源碼的留言聯繫小編免費領取即可!持續更新中...一、多態多態基於繼承(),現實事物中經常可以看到人,而學生、工人、白領、太空人等都是人,即由人演變而來的多種狀態。多態是Java三大特性(封裝、繼承、多態)之一,它的出現豐富了完善了Java面向對象體系。
  • Java常見內存溢出異常分析
    通過 java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 我們設置了堆內存為 10 兆, 並且使用參數 -XX:+HeapDumpOnOutOfMemoryError 讓 JVM 在發生 OutOfMemoryError 異常時列印出當前的內存快照以便於後續分析。
  • 什麼是JAVA反射機制,詳細解讀JAVA面試的核心技術
    Java中的反射機制是指在運行狀態中,對於任意一個類,能夠動態獲取這個類中的屬性和方法;對於任意一個對象,都能夠任意調用它的屬性和方法。這種動態獲取類的信息以及動態調用對象方法的功能稱為Java的反射機制。總結就是:反射可以實現運行時知道任意一個類的屬性和方法。二、Java當中為什麼需要反射機制?工作原理是什麼?