Java一行代碼搞定耗時(shí)性能追蹤
前言
在開發(fā)過(guò)程中,性能監(jiān)控和調(diào)試是我們經(jīng)常面對(duì)的問題。
雖然市面上有許多成熟的性能監(jiān)控工具,但有時(shí)我們需要一個(gè)輕量級(jí)、靈活且優(yōu)雅的解決方案。
當(dāng)然也可以自己手動(dòng)在業(yè)務(wù)代碼中進(jìn)行追蹤,比如先記錄startTime,執(zhí)行結(jié)束后再拿當(dāng)前時(shí)間減去startTime,計(jì)算出耗時(shí)。
但是畢竟會(huì)制造很多重復(fù)代碼。
本文將介紹如何設(shè)計(jì)和實(shí)現(xiàn)一個(gè)簡(jiǎn)潔而優(yōu)雅的TimeTracker工具類,它不僅能滿足基本的性能追蹤需求,還支持了函數(shù)式接口、try-with-resources等多種調(diào)用機(jī)制。
最初的痛點(diǎn)
還記得我們是怎么記錄代碼執(zhí)行時(shí)間的嗎?到處都是這樣的代碼:
long start = System.currentTimeMillis(); try { // 業(yè)務(wù)邏輯 } finally { // 計(jì)算耗時(shí) }
每次都得寫這種重復(fù)又啰嗦的代碼,要不就得復(fù)制粘貼,還容易漏掉,CV大法固然好,但懶人總想要更懶的方式。
進(jìn)化:擁抱 try-with-resources
偶然間,我想到了 AutoCloseable 接口,再想到每次處理流的時(shí)候,直接 try 里面一包,什么都不用關(guān)心,那是不是我也可以這樣處理執(zhí)行時(shí)間?
想象一下,如果能這樣寫,那豈不是很優(yōu)雅:
try (TimeTracker ignored = new TimeTracker("數(shù)據(jù)庫(kù)操作")) { // 業(yè)務(wù)代碼,耗時(shí)自動(dòng)搞定! }
瞬間,代碼變得清爽多了!資源自動(dòng)管理,耗時(shí)自動(dòng)計(jì)算,福音嘛這不是!
說(shuō)干就干,新建一個(gè) TimeTracker
類,實(shí)現(xiàn) AutoCloseable
,簡(jiǎn)單鼓搗一番,重點(diǎn)在于,在 close()
中計(jì)算耗時(shí),實(shí)現(xiàn)全自動(dòng)化。于是就有了第一版。
當(dāng)然,這才是剛開始。
Pro: 函數(shù)式接口
但是,還能更懶一點(diǎn)嗎?當(dāng)然可以!
不妨試試函數(shù)式接口!
比如下面這樣:
TimeTracker.track("用戶查詢", () -> { return userService.findById(123); });
連 try 都不用寫了!一行代碼搞定性能監(jiān)控,是不是很厲害?這下點(diǎn)題了不是!
什么?你說(shuō)這明明是3行?
那如果我這樣寫呢?
TimeTracker.track("操作", () -> riskyMethod());
這下沒毛病了吧
如果想要返回值,那也很簡(jiǎn)單,直接這樣寫:
String result = TimeTracker.track("簡(jiǎn)單任務(wù)", () -> { Thread.sleep(1000); return "完成"; });
和普通的調(diào)用沒有區(qū)別,毫無(wú)心智負(fù)擔(dān)。
Pro Max:異常處理
雖然現(xiàn)在一行就搞定了,但是缺少一個(gè)關(guān)鍵的功能,那就是異常處理。
考量一個(gè)程序員是否厲害的標(biāo)準(zhǔn),從來(lái)不是他能寫出多高大上的代碼,而且豐富的開發(fā)經(jīng)驗(yàn)和強(qiáng)大的問題追蹤能力。
因?yàn)檫@里怎么能缺少異常處理。
在上面的版本中,都沒有涉及異常,因?yàn)?.track()
內(nèi)部把異常消化掉并重新包裝成了 RuntimeException
。
public static <T> T track(String operationName, ThrowableSupplier<T> execution) { try { return trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("執(zhí)行失敗: " + operationName, e); } }
考慮到不同場(chǎng)景對(duì)于異常處理的需求不同,所以還得再額外提供一種模式,允許調(diào)用方顯式地進(jìn)行異常處理,把選擇權(quán)交給用戶。
比如下面這樣:
try { TimeTracker.trackThrows("操作", () -> { return riskyMethod(); // 保留原始異常 }); } catch (SpecificException e) { // 精確處理 }
那這樣就大功告成了。
完整代碼
下面這是完整代碼。
各種注釋都寫在里面,可以說(shuō)是非常詳細(xì)了。
包括使用示例,也寫在JavaDoc里面,真正做到注釋比代碼還多。
/** * 性能跟蹤工具類,用于測(cè)量代碼執(zhí)行時(shí)間并提供靈活的異常處理機(jī)制。 * * <p>主要特性: * <ul> * <li>精確測(cè)量代碼執(zhí)行時(shí)間</li> * <li>支持帶返回值和無(wú)返回值的方法跟蹤</li> * <li>提供兩種異常處理模式</li> * <li>支持自動(dòng)資源管理</li> * </ul> * * <h2>使用示例:</h2> * * <h3> try-with-resources 手動(dòng)跟蹤</h3> * <pre>{@code * // 手動(dòng)管理資源和性能跟蹤 * try (TimeTracker tracker = new TimeTracker("數(shù)據(jù)庫(kù)操作")) { * database.connect(); * database.executeQuery(); * } // 自動(dòng)關(guān)閉,并打印執(zhí)行時(shí)間 * * // 帶返回值的try-with-resources * try (TimeTracker tracker = new TimeTracker("復(fù)雜計(jì)算"); * Resource resource = acquireResource()) { * return performComplexCalculation(resource); * } * }</pre> * * <h3>結(jié)合靜態(tài)方法的try-with-resources</h3> * <pre>{@code * try (TimeTracker ignored = TimeTracker.of("網(wǎng)絡(luò)請(qǐng)求")) { * httpClient.sendRequest(); * httpClient.receiveResponse(); * } * }</pre> * * <p>注意:使用try-with-resources可以確保資源正確關(guān)閉, * 并自動(dòng)記錄執(zhí)行時(shí)間。</p> * * <h3>lambda自動(dòng)處理異常</h3> * <pre>{@code * // 無(wú)返回值方法 * TimeTracker.track("數(shù)據(jù)處理", () -> { * processData(); // 可能拋出異常的方法 * }); * * // 有返回值方法 * String result = TimeTracker.track("查詢用戶", () -> { * return userService.findById(123); * }); * }</pre> * * <h3>lambda顯式異常處理</h3> * <pre>{@code * try { * // 允許拋出原始異常 * String result = TimeTracker.trackThrows("復(fù)雜查詢", () -> { * return complexQuery(); // 可能拋出檢查異常 * }); * } catch (SQLException e) { * // 精確處理特定異常 * logger.error("數(shù)據(jù)庫(kù)查詢失敗", e); * } * }</pre> * * <h3>lambda嵌套使用</h3> * <pre>{@code * TimeTracker.track("整體流程", () -> { * // 子任務(wù)1 * TimeTracker.track("數(shù)據(jù)準(zhǔn)備", () -> prepareData()); * * // 子任務(wù)2 * return TimeTracker.track("數(shù)據(jù)處理", () -> processData()); * }); * }</pre> * * <p>注意:默認(rèn)情況下會(huì)打印執(zhí)行時(shí)間到控制臺(tái)。對(duì)于生產(chǎn)環(huán)境, * 建議根據(jù)需要自定義日志記錄機(jī)制。</p> * * @author [Your Name] * @version 1.0 * @since [版本號(hào)] */ public class TimeTracker implements AutoCloseable { /** 操作名稱 */ private final String operationName; /** 開始時(shí)間(納秒) */ private final long startTime; /** 是否啟用日志 */ private final boolean logEnabled; /** * 創(chuàng)建一個(gè)新的TimeTracker實(shí)例。 * * @param operationName 要跟蹤的操作名稱 */ public TimeTracker(String operationName) { this(operationName, true); } /** * 私有構(gòu)造函數(shù),用于創(chuàng)建TimeTracker實(shí)例。 * * @param operationName 操作名稱 * @param logEnabled 是否啟用日志輸出 */ private TimeTracker(String operationName, boolean logEnabled) { this.operationName = operationName; this.startTime = System.nanoTime(); this.logEnabled = logEnabled; if (logEnabled) { System.out.printf("開始執(zhí)行: %s%n", operationName); } } /** * 創(chuàng)建一個(gè)新的TimeTracker實(shí)例的靜態(tài)工廠方法。 * * @param operationName 要跟蹤的操作名稱 * @return 新的TimeTracker實(shí)例 */ public static TimeTracker of(String operationName) { return new TimeTracker(operationName); } /** * 跟蹤帶返回值的代碼塊執(zhí)行時(shí)間,異常會(huì)被包裝為RuntimeException。 * * @param operationName 操作名稱 * @param execution 要執(zhí)行的代碼塊 * @param <T> 返回值類型 * @return 代碼塊的執(zhí)行結(jié)果 * @throws RuntimeException 如果執(zhí)行過(guò)程中發(fā)生異常 */ public static <T> T track(String operationName, ThrowableSupplier<T> execution) { try { return trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("執(zhí)行失敗: " + operationName, e); } } /** * 跟蹤帶返回值的代碼塊執(zhí)行時(shí)間,允許拋出異常。 * * @param operationName 操作名稱 * @param execution 要執(zhí)行的代碼塊 * @param <T> 返回值類型 * @return 代碼塊的執(zhí)行結(jié)果 * @throws Exception 如果執(zhí)行過(guò)程中發(fā)生異常 */ public static <T> T trackThrows(String operationName, ThrowableSupplier<T> execution) throws Exception { try (TimeTracker ignored = new TimeTracker(operationName, true)) { return execution.get(); } } /** * 跟蹤無(wú)返回值的代碼塊執(zhí)行時(shí)間,異常會(huì)被包裝為RuntimeException。 * * @param operationName 操作名稱 * @param execution 要執(zhí)行的代碼塊 * @throws RuntimeException 如果執(zhí)行過(guò)程中發(fā)生異常 */ public static void track(String operationName, ThrowableRunnable execution) { try { trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("執(zhí)行失敗: " + operationName, e); } } /** * 跟蹤無(wú)返回值的代碼塊執(zhí)行時(shí)間,允許拋出異常。 * * @param operationName 操作名稱 * @param execution 要執(zhí)行的代碼塊 * @throws Exception 如果執(zhí)行過(guò)程中發(fā)生異常 */ public static void trackThrows(String operationName, ThrowableRunnable execution) throws Exception { try (TimeTracker ignored = new TimeTracker(operationName, true)) { execution.run(); } } @Override public void close() { if (logEnabled) { // 計(jì)算執(zhí)行時(shí)間(轉(zhuǎn)換為毫秒) long timeElapsed = (System.nanoTime() - startTime) / 1_000_000; System.out.printf("%s 執(zhí)行完成,耗時(shí): %d ms%n", operationName, timeElapsed); } } /** * 可拋出異常的Supplier函數(shù)式接口。 * * @param <T> 返回值類型 */ @FunctionalInterface public interface ThrowableSupplier<T> { /** * 獲取結(jié)果。 * * @return 執(zhí)行結(jié)果 * @throws Exception 如果執(zhí)行過(guò)程中發(fā)生錯(cuò)誤 */ T get() throws Exception; } /** * 可拋出異常的Runnable函數(shù)式接口。 */ @FunctionalInterface public interface ThrowableRunnable { /** * 執(zhí)行操作。 * * @throws Exception 如果執(zhí)行過(guò)程中發(fā)生錯(cuò)誤 */ void run() throws Exception; } }
一個(gè)DEMO
在JavaDoc里面已經(jīng)清楚寫明了調(diào)用示例,這里額外再補(bǔ)充一個(gè)Demo類,可能更清晰
import java.io.IOException; public class TimeTrackerDemo { public void demonstrateUsage() { // 1. 使用不拋出檢查異常的版本(異常被包裝為RuntimeException) TimeTracker.track("簡(jiǎn)單任務(wù)", () -> { Thread.sleep(1000); return "完成"; }); // 2. 使用可能拋出異常的版本 try { TimeTracker.trackThrows("可能失敗的任務(wù)", () -> { if (Math.random() < 0.5) { throw new IOException("模擬IO異常"); } return "成功"; }); } catch (Exception e) { // 處理異常 e.printStackTrace(); } // 3. 嵌套使用示例 try { TimeTracker.trackThrows("復(fù)雜流程", () -> { // 子任務(wù)1:使用不拋出異常的版本 TimeTracker.track("子任務(wù)1", () -> { Thread.sleep(500); }); // 子任務(wù)2:使用拋出異常的版本 return TimeTracker.trackThrows("子任務(wù)2", () -> { Thread.sleep(500); return "全部完成"; }); }); } catch (Exception e) { // 處理異常 e.printStackTrace(); } // 4. try-with-resources 示例 try (TimeTracker tracker = TimeTracker.of("資源管理演示")) { // 模擬資源操作 performResourceIntensiveTask(); } // 5. 多資源管理的try-with-resources try ( TimeTracker tracker1 = TimeTracker.of("第一階段"); TimeTracker tracker2 = TimeTracker.of("第二階段"); // 可以同時(shí)管理其他資源 CustomResource resource = acquireResource() ) { processResourcesSequentially(resource); } catch (Exception e) { // 異常處理 e.printStackTrace(); } // 6. 忽略返回值的try-with-resources try (TimeTracker ignored = TimeTracker.of("后臺(tái)任務(wù)")) { performBackgroundTask(); } } // 輔助方法(僅作示例) private void performResourceIntensiveTask() { Thread.sleep(1000); System.out.println("資源密集型任務(wù)完成"); } private CustomResource acquireResource() { return new CustomResource(); } private void processResourcesSequentially(CustomResource resource) { // 處理資源的示例方法 resource.process(); } private void performBackgroundTask() { // 后臺(tái)任務(wù)示例 System.out.println("執(zhí)行后臺(tái)任務(wù)"); } // 模擬自定義資源類 private static class CustomResource implements AutoCloseable { public void process() { System.out.println("處理資源"); } @Override public void close() { System.out.println("關(guān)閉資源"); } } }
改進(jìn)建議
當(dāng)然,這個(gè)類還有很大的改進(jìn)空間,我簡(jiǎn)單列幾個(gè),列位看官可以根據(jù)自己的真實(shí)場(chǎng)景再逐步進(jìn)行優(yōu)化。
- 集成日志框架,比如Slf4j,支持更靈活的輸出方式
- 添加更多的時(shí)間統(tǒng)計(jì)維度(最大值、最小值、平均值等)
- 添加性能指標(biāo)收集,支持監(jiān)控?cái)?shù)據(jù)統(tǒng)計(jì)
- 支持異步操作
革命尚未成功,同志仍需努力。
總結(jié)
一點(diǎn)點(diǎn)經(jīng)驗(yàn)
先來(lái)點(diǎn)經(jīng)驗(yàn)總結(jié),仁者見仁,智者見智。
- 工具類設(shè)計(jì)務(wù)必要注重實(shí)用性和易用性的平衡
- 工具類只是工具,千萬(wàn)不能在工具類中牽扯業(yè)務(wù)
- 異常處理需要考慮實(shí)際的真實(shí)的使用場(chǎng)景
- 合理使用語(yǔ)言特性,可以大大簡(jiǎn)化代碼
- 魯棒性非常重要
寫在最后
寫代碼這些年,常常要記錄些執(zhí)行時(shí)間。起初也是簡(jiǎn)單,System.currentTimeMillis()
放在前后,相減便知道耗了多少毫秒。后來(lái)覺得這樣寫著繁瑣,且容易忘記處理異常,索性就做了這么個(gè)工具類。
說(shuō)來(lái)也沒什么新奇的,不過(guò)是用了Java里的AutoCloseable接口,再配上lambda表達(dá)式,讓代碼看起來(lái)干凈些。倒是在處理異常時(shí)費(fèi)了點(diǎn)心思,畢竟實(shí)際開發(fā)中,異常處理往往比主要邏輯還要來(lái)得復(fù)雜。
回頭再看這段代碼,倒也不覺得有多少技術(shù)含量,但確實(shí)解決了實(shí)際問題。這大概就是寫程序的意思:不是為了寫出多么驚世駭俗的代碼,而是讓原本繁瑣的事情變得簡(jiǎn)單,讓使用者覺得舒服。
以上就是Java一行代碼搞定耗時(shí)性能追蹤的詳細(xì)內(nèi)容,更多關(guān)于Java耗時(shí)性能追蹤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Netty解碼http請(qǐng)求獲取URL亂碼問題
這篇文章主要介紹了解決Netty解碼http請(qǐng)求獲取URL亂碼問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06解決static類使用@Value獲取yml文件獲取不到的問題
在靜態(tài)類中直接使用@Value注解無(wú)法獲取yml文件中的配置,解決方案是在工具類Utils中創(chuàng)建靜態(tài)的setter方法,并從外部類ServiceClass中調(diào)用這個(gè)方法來(lái)設(shè)置值,這種方法通過(guò)外部調(diào)用來(lái)間接設(shè)置靜態(tài)變量的值,從而成功讀取yml配置2024-09-09Java基于反射機(jī)制實(shí)現(xiàn)全部注解獲取的方法示例
這篇文章主要介紹了Java基于反射機(jī)制實(shí)現(xiàn)全部注解獲取的方法,結(jié)合實(shí)例形式分析了java反射機(jī)制獲取注解的具體實(shí)現(xiàn)方法與操作注意事項(xiàng),需要的朋友可以參考下2019-09-09全網(wǎng)最全SpringBoot集成swagger的詳細(xì)教程
swagger是當(dāng)下比較流行的實(shí)時(shí)接口文文檔生成工具,swagger分為swagger2?和swagger3兩個(gè)常用版本,二者區(qū)別不是很大,主要對(duì)于依賴和注解進(jìn)行了優(yōu)化,swagger2需要引入2個(gè)jar包,swagger3只需要一個(gè),用起來(lái)沒有什么大的區(qū)別,本文給大家詳細(xì)介紹,感興趣的朋友一起看看吧2022-08-08解決SpringBoot使用devtools導(dǎo)致的類型轉(zhuǎn)換異常問題
這篇文章主要介紹了解決SpringBoot使用devtools導(dǎo)致的類型轉(zhuǎn)換異常問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。 一起跟隨小編過(guò)來(lái)看看吧2020-08-08PageHelper在springboot+mybatis框架中的使用步驟及原理解析
這篇文章主要介紹了PageHelper在springboot+mybatis框架中的使用步驟及原理解析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03