Java計(jì)時(shí)器工具StopWatch的具體使用
前言
平常,我們想要統(tǒng)計(jì)某一段代碼塊,或某一個(gè)方法的執(zhí)行時(shí)間,最簡(jiǎn)單的是采用如下的方式。
package com.chenpi; ? /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/2 */ public class ChenPi { ? public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); Thread.sleep(1000); long endTime = System.currentTimeMillis(); System.out.println("執(zhí)行耗時(shí)(ms):" + (endTime - startTime)); } ? } ? // 輸出結(jié)果如下 執(zhí)行耗時(shí)(ms):1011
但如果我們想對(duì)多個(gè)代碼段進(jìn)行計(jì)時(shí),以及每個(gè)階段計(jì)時(shí)的占比等信息,如果還是采用如上的方式就會(huì)充斥比較多的與業(yè)務(wù)無(wú)關(guān)的代碼,不夠簡(jiǎn)潔。
Spring StopWatch
Spring 框架有個(gè)工具類 StopWatch,它可以用來(lái)對(duì)程序中代碼塊,或者方法進(jìn)行計(jì)時(shí),并且支持多階段計(jì)時(shí),以及階段時(shí)間占比等統(tǒng)計(jì),使用起來(lái)代碼比較簡(jiǎn)潔,輕量。
package com.chenpi; ? import org.springframework.util.StopWatch; ? /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/2 */ public class ChenPi { ? public static void main(String[] args) throws InterruptedException { // 聲明一個(gè)計(jì)時(shí)器 StopWatch stopWatch = new StopWatch("陳皮的計(jì)時(shí)器"); // 開始計(jì)時(shí) stopWatch.start("發(fā)送MQ計(jì)時(shí)"); Thread.sleep(1000); // 結(jié)束計(jì)時(shí) stopWatch.stop(); // 打印統(tǒng)計(jì) System.out.println(stopWatch.prettyPrint()); } ? } ? ? // 輸出結(jié)果如下 StopWatch '陳皮的計(jì)時(shí)器': running time = 1005775500 ns --------------------------------------------- ns % Task name --------------------------------------------- 1005775500 100% 發(fā)送MQ計(jì)時(shí)
Spring StopWatch 有以下幾個(gè)常用方法:
- StopWatch():構(gòu)造一個(gè)計(jì)時(shí)器
- StopWatch(String id):構(gòu)造一個(gè)指定 id 的計(jì)時(shí)器
- start():創(chuàng)建一個(gè)名為空字符串的計(jì)時(shí)任務(wù),開始計(jì)時(shí)
- start(String taskName):創(chuàng)建一個(gè)指定名稱計(jì)時(shí)任務(wù),開始計(jì)時(shí)
- stop():結(jié)束當(dāng)前任務(wù)的計(jì)時(shí)
- getTotalTimeNanos():獲取全部任務(wù)的執(zhí)行時(shí)間,單位納秒
- getTotalTimeMillis():獲取全部任務(wù)的執(zhí)行時(shí)間,單位毫秒
- shortSummary():獲取簡(jiǎn)單的統(tǒng)計(jì)信息
- prettyPrint():以友好方式輸出總統(tǒng)計(jì)時(shí)間,以及各個(gè)階段任務(wù)的執(zhí)行時(shí)間
- setKeepTaskList(boolean keepTaskList):是否在內(nèi)部的列表中存儲(chǔ)每一個(gè)任務(wù)
實(shí)踐例子
當(dāng)程序中有多個(gè)計(jì)時(shí)器時(shí),可通過(guò)構(gòu)造不同 id 的計(jì)時(shí)器來(lái)區(qū)分。以下演示多個(gè)不同計(jì)時(shí)器,統(tǒng)計(jì)不同階段的執(zhí)行時(shí)間。
package com.chenpi; ? import org.springframework.util.StopWatch; ? /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/2 */ public class ChenPi { ? public static void main(String[] args) throws InterruptedException { m1(); m2(); } ? private static void m1() throws InterruptedException { ? // 聲明一個(gè)計(jì)時(shí)器 StopWatch stopWatch = new StopWatch("m1計(jì)時(shí)器"); ? stopWatch.start("查詢數(shù)據(jù)庫(kù)"); Thread.sleep(1000); stopWatch.stop(); ? stopWatch.start("邏輯計(jì)算"); Thread.sleep(500); stopWatch.stop(); ? System.out.println(stopWatch.prettyPrint()); } ? private static void m2() throws InterruptedException { ? // 聲明一個(gè)計(jì)時(shí)器 StopWatch stopWatch = new StopWatch("m2計(jì)時(shí)器"); ? stopWatch.start("遠(yuǎn)程調(diào)用"); Thread.sleep(800); stopWatch.stop(); ? stopWatch.start("發(fā)送MQ"); Thread.sleep(200); stopWatch.stop(); ? System.out.println(stopWatch.prettyPrint()); } } ? // 輸出結(jié)果如下 StopWatch 'm1計(jì)時(shí)器': running time = 1516953200 ns --------------------------------------------- ns % Task name --------------------------------------------- 1008091000 066% 查詢數(shù)據(jù)庫(kù) 508862200 034% 邏輯計(jì)算 ? StopWatch 'm2計(jì)時(shí)器': running time = 1013080000 ns --------------------------------------------- ns % Task name --------------------------------------------- 809345900 080% 遠(yuǎn)程調(diào)用 203734100 020% 發(fā)生MQ
源碼分析
其實(shí) StopWatch 底層實(shí)現(xiàn)很簡(jiǎn)單,對(duì)于每一個(gè)任務(wù),在任務(wù)開始和結(jié)束時(shí)刻調(diào)用System.*nanoTime*()
方法獲取服務(wù)器當(dāng)前的時(shí)間,然后計(jì)算每一個(gè)任務(wù)的執(zhí)行時(shí)間,存儲(chǔ)在內(nèi)部。內(nèi)部使用一個(gè)列表存儲(chǔ)不同任務(wù)階段的執(zhí)行時(shí)間,最后打印輸出。
package org.springframework.util; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.lang.Nullable; public class StopWatch { // 計(jì)時(shí)器id private final String id; // 是否將任務(wù)存儲(chǔ)到任務(wù)列表中 private boolean keepTaskList = true; // 存儲(chǔ)全部任務(wù)的列表 private final List<TaskInfo> taskList = new ArrayList<>(1); // 當(dāng)前任務(wù)開始時(shí)間 private long startTimeNanos; // 當(dāng)前任務(wù)名稱 @Nullable private String currentTaskName; // 最后一個(gè)任務(wù) @Nullable private TaskInfo lastTaskInfo; // 總?cè)蝿?wù)數(shù) private int taskCount; // 總的執(zhí)行時(shí)間 private long totalTimeNanos; // 構(gòu)造一個(gè)id為空字符串的計(jì)時(shí)器 public StopWatch() { this(""); } // 構(gòu)造一個(gè)指定id的計(jì)時(shí)器 public StopWatch(String id) { this.id = id; } // 獲取計(jì)時(shí)器id public String getId() { return this.id; } public void setKeepTaskList(boolean keepTaskList) { this.keepTaskList = keepTaskList; } // 開始計(jì)時(shí) public void start() throws IllegalStateException { start(""); } // 開始一個(gè)指定任務(wù)名稱的計(jì)時(shí) public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { throw new IllegalStateException("Can't start StopWatch: it's already running"); } this.currentTaskName = taskName; this.startTimeNanos = System.nanoTime(); } // 停止任務(wù)計(jì)時(shí) public void stop() throws IllegalStateException { if (this.currentTaskName == null) { throw new IllegalStateException("Can't stop StopWatch: it's not running"); } long lastTime = System.nanoTime() - this.startTimeNanos; this.totalTimeNanos += lastTime; this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime); if (this.keepTaskList) { this.taskList.add(this.lastTaskInfo); } ++this.taskCount; this.currentTaskName = null; } public boolean isRunning() { return (this.currentTaskName != null); } @Nullable public String currentTaskName() { return this.currentTaskName; } public long getLastTaskTimeNanos() throws IllegalStateException { if (this.lastTaskInfo == null) { throw new IllegalStateException("No tasks run: can't get last task interval"); } return this.lastTaskInfo.getTimeNanos(); } public long getLastTaskTimeMillis() throws IllegalStateException { if (this.lastTaskInfo == null) { throw new IllegalStateException("No tasks run: can't get last task interval"); } return this.lastTaskInfo.getTimeMillis(); } public String getLastTaskName() throws IllegalStateException { if (this.lastTaskInfo == null) { throw new IllegalStateException("No tasks run: can't get last task name"); } return this.lastTaskInfo.getTaskName(); } public TaskInfo getLastTaskInfo() throws IllegalStateException { if (this.lastTaskInfo == null) { throw new IllegalStateException("No tasks run: can't get last task info"); } return this.lastTaskInfo; } public long getTotalTimeNanos() { return this.totalTimeNanos; } public long getTotalTimeMillis() { return nanosToMillis(this.totalTimeNanos); } public double getTotalTimeSeconds() { return nanosToSeconds(this.totalTimeNanos); } public int getTaskCount() { return this.taskCount; } public TaskInfo[] getTaskInfo() { if (!this.keepTaskList) { throw new UnsupportedOperationException("Task info is not being kept!"); } return this.taskList.toArray(new TaskInfo[0]); } public String shortSummary() { return "StopWatch '" + getId() + "': running time = " + getTotalTimeNanos() + " ns"; } public String prettyPrint() { StringBuilder sb = new StringBuilder(shortSummary()); sb.append('\n'); if (!this.keepTaskList) { sb.append("No task info kept"); } else { sb.append("---------------------------------------------\n"); sb.append("ns % Task name\n"); sb.append("---------------------------------------------\n"); NumberFormat nf = NumberFormat.getNumberInstance(); nf.setMinimumIntegerDigits(9); nf.setGroupingUsed(false); NumberFormat pf = NumberFormat.getPercentInstance(); pf.setMinimumIntegerDigits(3); pf.setGroupingUsed(false); for (TaskInfo task : getTaskInfo()) { sb.append(nf.format(task.getTimeNanos())).append(" "); sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" "); sb.append(task.getTaskName()).append('\n'); } } return sb.toString(); } @Override public String toString() { StringBuilder sb = new StringBuilder(shortSummary()); if (this.keepTaskList) { for (TaskInfo task : getTaskInfo()) { sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeNanos()).append(" ns"); long percent = Math.round(100.0 * task.getTimeNanos() / getTotalTimeNanos()); sb.append(" = ").append(percent).append('%'); } } else { sb.append("; no task info kept"); } return sb.toString(); } private static long nanosToMillis(long duration) { return TimeUnit.NANOSECONDS.toMillis(duration); } private static double nanosToSeconds(long duration) { return duration / 1_000_000_000.0; } // 任務(wù)實(shí)體 public static final class TaskInfo { // 任務(wù)名稱 private final String taskName; // 任務(wù)執(zhí)行時(shí)間 private final long timeNanos; TaskInfo(String taskName, long timeNanos) { this.taskName = taskName; this.timeNanos = timeNanos; } public String getTaskName() { return this.taskName; } public long getTimeNanos() { return this.timeNanos; } public long getTimeMillis() { return nanosToMillis(this.timeNanos); } public double getTimeSeconds() { return nanosToSeconds(this.timeNanos); } } } 復(fù)制代碼
StopWatch 使用起來(lái)簡(jiǎn)潔,支持多任務(wù)階段統(tǒng)計(jì),統(tǒng)計(jì)多任務(wù)時(shí)間占比等,統(tǒng)計(jì)結(jié)果直觀。但是它也有不好的地方,就是一個(gè) StopWatch 實(shí)例只能同時(shí) start 一個(gè) task,只能等這個(gè) task 進(jìn)行 stop 之后,才能繼續(xù) start 另一個(gè) task。注意,StopWatch 實(shí)例不是線程安全的,也沒(méi)必要進(jìn)行同步處理。
lang3 StopWatch
Apache commons lang3 包下也有一個(gè)用于計(jì)時(shí)工具類 StopWatch。它還有暫停計(jì)時(shí),恢復(fù)計(jì)時(shí),設(shè)置分割點(diǎn)等功能。
org.apache.commons:commons-lang3:3.12.0 復(fù)制代碼
它主要有以下幾個(gè)常用方法:
- create():實(shí)例化一個(gè)計(jì)時(shí)器
- createStarted():實(shí)例化一個(gè)計(jì)時(shí)器,并開始計(jì)時(shí)
- StopWatch(final String message):實(shí)例化一個(gè)帶有標(biāo)識(shí)符的計(jì)時(shí)器
- start():開始計(jì)時(shí)
- split():設(shè)置分割點(diǎn)
- getSplitTime():統(tǒng)計(jì)從 start 開始最后一個(gè)分割點(diǎn)的用時(shí)
- reset():重置計(jì)時(shí)
- suspend():暫停計(jì)時(shí)
- resume():恢復(fù)計(jì)時(shí)
- stop():停止計(jì)時(shí)
- getTime():統(tǒng)計(jì)從 start 到當(dāng)前時(shí)刻的同時(shí)
package com.chenpi; ? import org.apache.commons.lang3.time.StopWatch; ? /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/2 */ public class ChenPi { ? public static void main(String[] args) throws InterruptedException { ? // 聲明一個(gè)計(jì)時(shí)器 StopWatch stopWatch = new StopWatch("m1計(jì)時(shí)器"); ? stopWatch.start(); Thread.sleep(1000); System.out.println("start開始到現(xiàn)在的時(shí)間:" + stopWatch.getTime()); ? stopWatch.split(); Thread.sleep(500); System.out.println("start開始到最后一個(gè)split的時(shí)間:" + stopWatch.getSplitTime()); ? stopWatch.split(); Thread.sleep(500); System.out.println("start開始到最后一個(gè)split的時(shí)間:" + stopWatch.getSplitTime()); ? // 重置計(jì)時(shí) stopWatch.reset(); Thread.sleep(2000); stopWatch.start(); Thread.sleep(1000); System.out.println("start開始到現(xiàn)在的時(shí)間:" + stopWatch.getTime()); ? // 暫停計(jì)時(shí) stopWatch.suspend(); Thread.sleep(3000); // 恢復(fù)計(jì)時(shí) stopWatch.resume(); ? Thread.sleep(1000); ? // 結(jié)束計(jì)時(shí) stopWatch.stop(); ? Thread.sleep(1000); ? System.out.println("start開始到stop結(jié)束的時(shí)間:" + stopWatch.getTime()); ? System.out.println(stopWatch); } } ? // 輸出結(jié)果如下 start開始到現(xiàn)在的時(shí)間:1000 start開始到最后一個(gè)split的時(shí)間:1001 start開始到最后一個(gè)split的時(shí)間:1510 start開始到現(xiàn)在的時(shí)間:1004 start開始到stop結(jié)束的時(shí)間:2015 m1計(jì)時(shí)器 00:00:02.015
總結(jié)
- 如果是簡(jiǎn)單的計(jì)算執(zhí)行計(jì)時(shí),可以使用 JDK 自帶的類獲取系統(tǒng)時(shí)間進(jìn)行計(jì)時(shí)。
- 如果需要多階段計(jì)時(shí),并且需要統(tǒng)計(jì)每個(gè)階段的執(zhí)行時(shí)間占比等信息,可以使用 StopWatch 工具類。
- 推薦使用 Spring StopWatch,因?yàn)楸旧砦覀冺?xiàng)目使用 Spring 框架比較多,這樣就自帶了 StopWatch。
到此這篇關(guān)于Java計(jì)時(shí)器工具StopWatch的具體使用的文章就介紹到這了,更多相關(guān)Java計(jì)時(shí)器工具StopWatch內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于SpringCloud的微服務(wù)結(jié)構(gòu)及微服務(wù)遠(yuǎn)程調(diào)用
Spring Cloud 是一套完整的微服務(wù)解決方案,基于 Spring Boot 框架,準(zhǔn)確的說(shuō),它不是一個(gè)框架,而是一個(gè)大的容器,它將市面上較好的微服務(wù)框架集成進(jìn)來(lái),從而簡(jiǎn)化了開發(fā)者的代碼量,需要的朋友可以參考下2023-05-05Spring AOP 對(duì)象內(nèi)部方法間的嵌套調(diào)用方式
這篇文章主要介紹了Spring AOP 對(duì)象內(nèi)部方法間的嵌套調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08IDEA啟動(dòng)服務(wù)提示端口被占用,Web?server?failed?to?start.Port?was?al
這篇文章主要介紹了IDEA啟動(dòng)服務(wù)提示端口被占用,Web?server?failed?to?start.Port?was?already?in?use.,本文給大家分享解決方案,分為linux系統(tǒng)和windows系統(tǒng)解決方案,需要的朋友可以參考下2023-07-07IDEA下SpringBoot指定環(huán)境、配置文件啟動(dòng)操作過(guò)程
這篇文章主要介紹了IDEA下SpringBoot指定環(huán)境、配置文件啟動(dòng)過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08ssm框架Springmvc文件上傳實(shí)現(xiàn)代碼詳解
這篇文章主要介紹了ssm框架Springmvc文件上傳實(shí)現(xiàn)代碼詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Java創(chuàng)建型設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)
當(dāng)系統(tǒng)所提供的工廠所需生產(chǎn)的具體產(chǎn)品并不是一個(gè)簡(jiǎn)單的對(duì)象,而是多個(gè)位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中屬于不同類型的具體產(chǎn)品時(shí)需要使用抽象工廠模式,抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)2022-09-09