日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java并发编程—定时器Timer底层原理

發布時間:2024/4/15 java 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程—定时器Timer底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文作者:妮蔻

原文地址:Java并發編程筆記之Timer源碼分析

目錄

一、timer問題復現

二、Timer 實現原理分析


timer在JDK里面,是很早的一個API了。具有延時的,并具有周期性的任務,在newScheduledThreadPool出來之前我們一般會用Timer和TimerTask來做,但是Timer存在一些缺陷,為什么這么說呢?

  • Timer只創建唯一的線程來執行所有Timer任務。如果一個timer任務的執行很耗時,會導致其他TimerTask的時效準確性出問題。例如一個TimerTask每10秒執行一次,而另外一個TimerTask每40ms執行一次,重復出現的任務會在后來的任務完成后快速連續的被調用4次,要么完全“丟失”4次調用。
  • Timer的另外一個問題在于,如果TimerTask拋出未檢查的異常會終止timer線程。這種情況下,Timer也不會重新回復線程的執行了;它錯誤的認為整個Timer都被取消了。此時已經被安排但尚未執行的TimerTask永遠不會再執行了,新的任務也不能被調度了。

一、timer問題復現

這里做了一個小的 demo 來復現問題,代碼如下:

package com.hjc;import java.util.Timer; import java.util.TimerTask;public class TimerTest {//創建定時器對象static Timer timer = new Timer();public static void main(String[] args) {//添加任務1,延遲500ms執行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("---one Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}throw new RuntimeException("error ");}}, 500);//添加任務2,延遲1000ms執行timer.schedule(new TimerTask() {@Overridepublic void run() {for (;;) {System.out.println("---two Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}, 1000);} }

如上代碼先添加了一個任務在 500ms 后執行,然后添加了第二個任務在 1s 后執行,我們期望的是當第一個任務輸出 ---one Task--- 后等待 1s 后第二個任務會輸出 ---two Task---,

但是執行完畢代碼后輸出結果如下所示:

例子2:

public class Shedule {private static long start;public static void main(String[] args) {TimerTask task = new TimerTask() {public void run() {System.out.println(System.currentTimeMillis()-start);try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}};TimerTask task1 = new TimerTask() {@Overridepublic void run() {System.out.println(System.currentTimeMillis()-start);}};Timer timer = new Timer();start = System.currentTimeMillis();//啟動一個調度任務,1S鐘后執行timer.schedule(task,1000);//啟動一個調度任務,3S鐘后執行timer.schedule(task1,3000);}}

上面程序我們預想是第一個任務執行后,第二個任務3S后執行的,即輸出一個1000,一個3000.。實際運行結果如下:

實際運行結果并不如我們所愿。實際結果,是過了4S后才輸出第二個任務,即4001約等于4秒。那部分時間時間到哪里去了呢?那個時間是被我們第一個任務的sleep所占用了。

現在我們在第一個任務中去掉Thread.sleep();這一行代碼,運行是否正確了呢?運行結果如下:

可以看到確實是第一個任務過了1S后執行,第二個任務在第一個任務執行完后過3S執行了。這就說明了Timer只創建唯一的線程來執行所有Timer任務。如果一個timer任務的執行很耗時,會導致其他TimerTask的時效準確性出問題

二、Timer 實現原理分析

下面簡單介紹下 Timer 的原理,如下圖是 Timer 的原理模型介紹:

  • 其中 TaskQueue 是一個平衡二叉樹堆實現的優先級隊列,每個 Timer 對象內部有唯一一個 TaskQueue 隊列。用戶線程調用 timer 的 schedule 方法就是把 TimerTask 任務添加到 TaskQueue 隊列,在調用 schedule 的方法時候 long delay 參數用來說明該任務延遲多少時間執行。
  • TimerThread 是具體執行任務的線程,它從 TaskQueue 隊列里面獲取優先級最小的任務進行執行,需要注意的是只有執行完了當前的任務才會從隊列里面獲取下一個任務而不管隊列里面是否有已經到了設置的 delay 時間,一個 Timer 只有一個 TimerThread 線程,所以可知 Timer 的內部實現是一個多生產者單消費者模型。
  • 從實現模型可以知道要探究上面的問題只需看 TimerThread 的實現就可以了,TimerThread 的 run 方法主要邏輯源碼如下:

    public void run() {try {mainLoop();} finally {// 有人殺死了這個線程,表現得好像Timer已取消synchronized(queue) {newTasksMayBeScheduled = false;queue.clear(); // 消除過時的引用}} }private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;//從隊列里面獲取任務時候要加鎖synchronized(queue) {......}if (taskFired) task.run();//執行任務} catch(InterruptedException e) {}}}

    可知當任務執行過程中拋出了除 InterruptedException 之外的異常后,唯一的消費線程就會因為拋出異常而終止,那么隊列里面的其他待執行的任務就會被清除。所以 TimerTask 的 run 方法內最好使用 try-catch 結構 catch 主可能的異常,不要把異常拋出到 run 方法外

    其實要實現類似 Timer 的功能使用 ScheduledThreadPoolExecutor 的 schedule 是比較好的選擇。ScheduledThreadPoolExecutor 中的一個任務拋出了異常,其他任務不受影響的。

    ScheduledThreadPoolExecutor 例子如下:

    public class ScheduledThreadPoolExecutorTest {static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);public static void main(String[] args) {scheduledThreadPoolExecutor.schedule(new Runnable() {public void run() {System.out.println("---one Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}throw new RuntimeException("error ");}}, 500, TimeUnit.MICROSECONDS);scheduledThreadPoolExecutor.schedule(new Runnable() {public void run() {for (int i =0;i<5;++i) {System.out.println("---two Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}, 1000, TimeUnit.MICROSECONDS);scheduledThreadPoolExecutor.shutdown();} }

    運行結果如下:

    之所以 ScheduledThreadPoolExecutor 的其他任務不受拋出異常的任務的影響是因為 ScheduledThreadPoolExecutor 中的 ScheduledFutureTask 任務中 catch 掉了異常,但是在線程池任務的 run 方法內使用 catch 捕獲異常并打印日志是最佳實踐。

    總結

    以上是生活随笔為你收集整理的Java并发编程—定时器Timer底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。