Apache和Spring提供的StopWatch執行時間監視器

来源:https://www.cnblogs.com/fangshixiang/archive/2019/07/28/11261503.html
-Advertisement-
Play Games

一個可以沉迷於技術的程式猿,wx加入加入技術群:fsx641385712 ...


相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受贊譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本


前言

編碼過程中我們經常會希望得到一段代碼(一個方法)的執行時間,本文將介紹兩種時間監視器(秒錶)來讓你優雅的、靈活的處理這個問題。

Java源生方式

這種方式最最簡單,最好理解,當然也是最為常用:我們自己書寫。
例如:我們如果要統計一段代碼的執行時間,經常會這麼來寫:

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();   //獲取開始時間

        //函數主體代碼
        //...

        long endTime = System.currentTimeMillis(); //獲取結束時間
        System.out.println("程式運行時間: " + (endTime - startTime) + "ms");
    }

大多數時候我們使用ms來表示即可,但是這麼寫缺乏靈活性。倘若我們要展示成納秒、秒、甚至分鐘,還得我們自己處理(把毫秒值拿來進行轉換~ )

當然可能到了JDK8以後,我們這麼做能變得稍微靈活一些:可以這麼處理:

    public static void main(String[] args) {
        Instant start = Instant.now();
        //doSomething();
        Instant end = Instant.now();

        Duration duration = Duration.between(start, end);
        System.out.println("millis = " + duration.toMillis());
    }

這個比上面靈活度強一些。但也還是有一定的缺點:步驟稍顯複雜,總體上還是不夠優雅,也不是那麼的靈活。
那麼本文針對此問題介紹一個工具:StopWatch執行時間監視器。藉助它來統計我們程式的執行時間,帶給非常多的方便和優雅。

StopWatch需要依賴額外的Jar:commons-lang3或者spring-core,但因這兩個Jar是Java開發中都必導的,因此依賴相容性方面可以忽略

StopWatch有很多開源的框架都有提供類似的功能:比如Apache的commons-lang3,當然還有Spring framwork自己提供的,本文將針對此倆分別做介紹~

Commons-lang3的StopWatch

Apache提供的這個任務執行監視器功能豐富強大(比Spring的強大),靈活性強,如下經典實用案例:

    public static void main(String[] args) throws Exception {
        StopWatch watch = StopWatch.createStarted(); //創建後立即start,常用
        //StopWatch watch = new StopWatch();
        //watch.start();

        Thread.sleep(1000);
        System.out.println("統計從開始到現在運行時間:" + watch.getTime() + "ms"); //1000ms

        Thread.sleep(1000);
        watch.split();
        System.out.println("從start到此刻為止的時間:" + watch.getTime());
        System.out.println("從開始到第一個切入點運行時間:" + watch.getSplitTime()); //2245

        Thread.sleep(1000);
        watch.split();
        System.out.println("從開始到第二個切入點運行時間:" + watch.getSplitTime());

        watch.reset(); //重置後必須使用start方法
        watch.start();
        Thread.sleep(1000);
        System.out.println("重新開始後到當前運行時間是:" + watch.getTime()); //1000

        watch.suspend(); //暫停
        Thread.sleep(6000); //模擬暫停6秒鐘

        watch.resume(); //上面suspend,這裡要想重新統計,需要恢復一下
        System.out.println("恢復後執行的時間是:" + watch.getTime()); //1000  註意此時這個值還是1000

        watch.stop();
        System.out.println("花費的時間》》" + watch.getTime() + "ms"); //1002ms
        System.out.println("花費的時間》》" + watch.getTime(TimeUnit.SECONDS) + "s"); //1s 可以直接轉成s

    }

列印結果:

統計從開始到現在運行時間:1007ms
從start到此刻為止的時間:2008
從開始到第一個切入點運行時間:2008
從開始到第二個切入點運行時間:3009
重新開始後到當前運行時間是:1000
恢復後執行的時間是:1000
花費的時間》》1001ms
花費的時間》》1s

如上就是StopWatch的基本使用方法,足以見到了它的強大吧,當然使用起來複雜度也是提升了些的。

核心原理解釋

原理相對簡單,簡單看看源碼便知:

// @since 2.0
public class StopWatch {
    // @since 3.5  這個靜態方法出現得稍微晚點哦~
    public static StopWatch createStarted() {
        final StopWatch sw = new StopWatch();
        sw.start();
        return sw;
    }

    // 這些成員變數是實現的核心~~~~~~~~~~~~~~
    private State runningState = State.UNSTARTED;
    private SplitState splitState = SplitState.UNSPLIT;
    private long startTime;
    // 思考:為何有了nonaTime這裡還得記錄一個Millis Time呢???
    // 因為nanoTime只能拿來計算差值(耗時) 但是getStartTime()這個老API還得靠MillsTime~~~
    private long startTimeMillis;
    private long stopTime;
    
    // 可見:start方法可不是能夠多次調用的哦~~和狀態是有關的
    public void start() {
        if (this.runningState == State.STOPPED) {
            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
        }
        if (this.runningState != State.UNSTARTED) {
            throw new IllegalStateException("Stopwatch already started. ");
        }
        this.startTime = System.nanoTime();
        this.startTimeMillis = System.currentTimeMillis();
        this.runningState = State.RUNNING;
    }

    // 停表時,最重要的是記錄下了stopTime 的值~~~然後標記狀態
    public void stop() {
        if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
            throw new IllegalStateException("Stopwatch is not running. ");
        }
        if (this.runningState == State.RUNNING) {
            this.stopTime = System.nanoTime();
        }
        this.runningState = State.STOPPED;
    }

    // 狀態變為非開始狀態...
    public void reset() {
        this.runningState = State.UNSTARTED;
        this.splitState = SplitState.UNSPLIT;
    }

    // 暫停:stopTime 也給了一個值
    public void suspend() {
        if (this.runningState != State.RUNNING) {
            throw new IllegalStateException("Stopwatch must be running to suspend. ");
        }
        this.stopTime = System.nanoTime();
        this.runningState = State.SUSPENDED;
    }

    // 這兩個方法是獲取差值的
    public long getTime() {
        return getNanoTime() / NANO_2_MILLIS;
    }
    // @since 3.5
    public long getTime(final TimeUnit timeUnit) {
        return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
    }

    // @since 2.4 老API  這叫獲取啟動的時間(啥時候啟動的)
    public long getStartTime() {
        if (this.runningState == State.UNSTARTED) {
            throw new IllegalStateException("Stopwatch has not been started");
        }
        // System.nanoTime is for elapsed time
        return this.startTimeMillis;
    }
}

可以看到原理是很簡單的,無非就是包裝了暫停、回覆、split等功能嘛

使用細節

==getTimegetSplitTime有啥區別呢?==
為了說明問題,此處我們看看getNanoTime()getSplitNanoTime()亦可:

    public long getNanoTime() {
        if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
            return this.stopTime - this.startTime;
        } else if (this.runningState == State.UNSTARTED) {
            return 0;
        } else if (this.runningState == State.RUNNING) {
            return System.nanoTime() - this.startTime;
        }
        throw new RuntimeException("Illegal running state has occurred.");
    }

    public long getSplitNanoTime() {
        if (this.splitState != SplitState.SPLIT) {
            throw new IllegalStateException("Stopwatch must be split to get the split time. ");
        }
        return this.stopTime - this.startTime;
    }

我們發現:

  • 調用getSplit...相關方法前,必須先調用Split方法

spilit()方法源碼如下:

    public void split() {
        if (this.runningState != State.RUNNING) {
            throw new IllegalStateException("Stopwatch is not running. ");
        }
        this.stopTime = System.nanoTime();
        this.splitState = SplitState.SPLIT;
    }

在調用split方法後,watch的狀態改為了SPLIT且,且,且stopTime 設置為了當前時間。因此此處我們的stopTime停止了,這個時候調用getSplitNanoTime(),返回的是start到split那時的時間差值。因此用此方法可以插入先停止stopTime()(有點插隊的趕腳),最後再輸出(先插好隊,最後在輸出)~

getTime()就是拿當前的時間戳,減去startTime,一般不涉及到stopTime的值,因此splitTime處理計算時間顯然更加的靈活,但是,一般我們使用getTime()就足夠了

Spring的StopWatch

Spring提供的這個任務監視器,我還是蠻喜歡使用的,因為一個它能夠幫我同事監控多個任務,使用起來也很方便。先看一個簡單的使用案例:

註意:一個監視器能夠記錄多個任務的執行時間這個特點非常重要哦~
比如:我們可以記錄多段代碼耗時時間,然後一次性列印~

    public static void main(String[] args) throws Exception {
        // 強烈每一個秒錶都給一個id,這樣查看日誌起來能夠更加的精確
        // 至於Id 我覺得給UUID是可行的~
        StopWatch sw = new StopWatch(UUID.randomUUID().toString());

        sw.start("起床");
        Thread.sleep(1000);
        System.out.println("當前任務名稱:" + sw.currentTaskName());
        sw.stop();

        sw.start("洗漱");
        Thread.sleep(2000);
        System.out.println("當前任務名稱:" + sw.currentTaskName());
        sw.stop();

        sw.start("鎖門");
        Thread.sleep(500);
        System.out.println("當前任務名稱:" + sw.currentTaskName());
        sw.stop();

        System.out.println(sw.prettyPrint()); // 這個方法列印在我們記錄日誌時是非常友好的  還有百分比的分析哦
        System.out.println(sw.shortSummary());
        System.out.println(sw.currentTaskName()); // stop後它的值為null


        // 最後一個任務的相關信息
        System.out.println(sw.getLastTaskName());
        System.out.println(sw.getLastTaskInfo());

        // 任務總的耗時  如果你想獲取到每個任務詳情(包括它的任務名、耗時等等)可使用
        System.out.println("所有任務總耗時:" + sw.getTotalTimeMillis());
        System.out.println("任務總數:" + sw.getTaskCount());
        System.out.println("所有任務詳情:" + sw.getTaskInfo()); // 拿到所有的任務
    }

列印:

當前任務名稱:起床
當前任務名稱:洗漱
當前任務名稱:鎖門
StopWatch 'd6ba9412-d551-4ba7-8b0e-1b7ccb42855d': running time (millis) = 3504
-----------------------------------------
ms     %     Task name
-----------------------------------------
01001  029%  起床
02000  057%  洗漱
00503  014%  鎖門

StopWatch 'd6ba9412-d551-4ba7-8b0e-1b7ccb42855d': running time (millis) = 3504
null
鎖門
org.springframework.util.StopWatch$TaskInfo@2d554825
所有任務總耗時:3504
任務總數:3
所有任務詳情:[Lorg.springframework.util.StopWatch$TaskInfo;@68837a77

我個人偏愛使用Spring提供的這個監視器,是因為它提供的prettyPrint()列印在日誌里進行分析可以非常的直觀,並且我覺得提供的多任務支持也更加實用一點,當然僅僅個人偏好而已~

最後

很多時候,寫代碼也是一種藝術,而藉助這種實用工具我就覺得藝術感更強些。希望我們能有追求更加美好事物的心,這點對於接納新知識特別重要。此處推薦這個監視器來代替之前的的使用,能讓小伙伴們更加靈活的分析你的代碼~

知識交流

若文章格式混亂,可點擊原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接

==The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==

若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。並且備註:"java入群" 字樣,會手動邀請入群


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 零基礎java開發工程師視頻教程全套,基礎+進階+項目實戰(152G) 共130天,152G 下載地址 ...
  • 周末閑來無事,看到隔壁家的老王在和隔壁家的媳婦玩24點,就進屋看了看。發現老王是真不行啊,那不行,這也不行。 就連個24點都玩不過他媳婦,給他媳婦氣的,啥都不能滿足,這不能,那也不能。 我坐下來和他媳婦玩了兩把,那都是無出其右,把把贏! 我要走的時候,他媳婦還輓留我多玩幾把,有意思。 為了能... ...
  • 書評: 感謝作者和譯者,很好的手把手的一個新手編程體驗書,消除編程物質恐懼感,線上看的liam huang翻譯的版,不確定看的是第幾版,有一些加分題沒有做,第五十題黑手黨外星人飛船做起來有點壓力,準備轉去codeacademy / think python和a byte of python在進行一些 ...
  • 前段時間自己使用 redis 開發的時候,搞了一個 docker ,然後直接開放連接沒有密碼,其實一開始我就知道會被黑產掃到然後給我種馬,但是把因為也是測試服務,其實也沒怎麼上心,於是就放任自由了,結果第二天果然收到了一份新鮮的木馬。然後簡單對其入侵做了一個分析,結果發現沒有能攻擊成功,但是既然木馬 ...
  • Spring 可以說是最流行的 Java 框架之一,也是一隻需要馴服的強大野獸。雖然它的基本概念相當容易掌握,但成為一名強大的 Spring 開發者仍需要很多時間和努力。 在本文中,我們將介紹 Spring 中一些常見的錯誤,特別是面向 Web 應用程式和 Spring Boot。正如 Spring ...
  • Java8 增加了 Lambda 表達式,很大程度使代碼變的更加簡潔緊湊了,那麼 Java8 是如何實現 Lambda 表達式的呢? 直接看一個簡單的創建線程的例子。 執行 編譯生成文件 ,然後用 命令來分析這個class文件。 執行 顯示所有類和成員。 由上面的代碼可以看出編譯器根據 Lambda ...
  • 一、原型模式簡介 1、基礎概念 原型模式屬於對象的創建模式。通過給出一個原型對象來指明所有創建的對象的類型,然後用複製這個原型對象的辦法創建出更多同類型的對象。 2、模式結構 原型模式要求對象實現一個可以“克隆”自身的介面,這樣就可以通過複製一個實例對象本身來創建一個新的實例。這樣一來,通過原型實例 ...
  • https://www.cnblogs.com/fireflyupup/p/4875130.html Collection List 在Collection的基礎上引入了有序的概念,位置精確;允許相同元素。在列表上迭代通常優於索引遍歷。特殊的ListIterator迭代器允許元素插入、替換,雙向訪問 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...