在我們的實際開發中,多多少少會遇到統計一段代碼片段的耗時的情況,我們一般的寫法如下
long start = System.currentTimeMillis();
try {
// .... 具體的代碼段
} finally {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
上面的寫法沒有什么毛病,但是看起來就不太美觀了,那么有沒有什么更優雅的寫法呢?
1. 代理方式
了解 Spring AOP 的同學可能立馬會想到一個解決方法,如果想要統計某個方法耗時,使用切面可以無侵入的實現,如
// 定義切點,攔截所有滿足條件的方法
@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void point() {
}
@Around("point()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try{
return joinPoint.proceed();
} finally {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
Spring AOP 的底層支持原理為代理模式,為目標對象提供增強功能;在 Spring 的生態體系下,使用 aop 的方式來統計方法耗時,可以說少侵入且實現簡單,但是有以下幾個問題
- 統計粒度為方法級別
- 類內部方法調用無法生效(詳情可以參考博文:【SpringBoot 基礎系列教程】AOP 之高級使用技能)
2. AutoCloseable
在 JDK1.7 引入了一個新的接口AutoCloseable
, 通常它的實現類配合try{}
使用,可在 IO 流的使用上,經常可以看到下面這種寫法
// 讀取文件內容並輸出
try (Reader stream = new BufferedReader(new InputStreamReader(new FileInputStream("/tmp")))) {
List<String> list = ((BufferedReader) stream).lines().collect(Collectors.toList());
System.out.println(list);
} catch (IOException e) {
e.printStackTrace();
}
注意上面的寫法中,最值得關注一點是,不需要再主動的寫stream.close
了,主要原因就是在try(){}
執行完畢之后,會調用方法AutoCloseable#close
方法;
基於此,我們就會有一個大單的想法,下一個Cost
類實現AutoCloseable
接口,創建時記錄一個時間,close 方法中記錄一個時間,並輸出時間差值;將需要統計耗時的邏輯放入try(){}
代碼塊
下面是一個具體的實現:
public static class Cost implements AutoCloseable {
private long start;
public Cost() {
this.start = System.currentTimeMillis();
}
@Override
public void close() {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
public static void testPrint() {
for (int i = 0; i < 5; i++) {
System.out.println("now " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try (Cost c = new Cost()) {
testPrint();
}
System.out.println("------over-------");
}
執行后輸出如下:
now 0
now 1
now 2
now 3
now 4
cost: 55
------over-------
如果代碼塊拋異常,也會正常輸出耗時么?
public static void testPrint() {
for (int i = 0; i < 5; i++) {
System.out.println("now " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 3) {
throw new RuntimeException("some exception!");
}
}
}
再次輸出如下,並沒有問題
now 0
now 1
now 2
now 3
cost: 46
Exception in thread "main" java.lang.RuntimeException: some exception!
at com.git.hui.boot.order.Application.testPrint(Application.java:43)
at com.git.hui.boot.order.Application.main(Application.java:50)
3. 小結
除了上面介紹的兩種方式,還有一種在業務開發中不太常見,但是在中間件、偏基礎服務的功能組件中可以看到,利用 Java Agent 探針技術來實現,比如阿里的 arthas 就是在 JavaAgent 的基礎上做了各種上天的功能,后續介紹 java 探針技術時會專門介紹
下面小結一下三種統計耗時的方式
基本寫法
long start = System.currentTimeMillis();
try {
// .... 具體的代碼段
} finally {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
優點是簡單,適用范圍廣泛;缺點是侵入性強,大量的重復代碼
Spring AOP
在 Spring 生態下,可以借助 AOP 來攔截目標方法,統計耗時
@Around("...")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try{
return joinPoint.proceed();
} finally {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
優點:無侵入,適合統一管理(比如測試環境輸出統計耗時,生產環境不輸出);缺點是適用范圍小,且粒度為方法級別,並受限於 AOP 的使用范圍
AutoCloseable
這種方式可以看做是第一種寫法的進階版
// 定義類
public static class Cost implements AutoCloseable {
private long start;
public Cost() {
this.start = System.currentTimeMillis();
}
@Override
public void close() {
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
// 使用姿勢
try (Cost c = new Cost()) {
...
}
優點是:簡單,適用范圍廣泛,且適合統一管理;缺點是依然有代碼侵入
說明
上面第二種方法看着屬於最優雅的方式,但是限制性強;如果有更靈活的需求,建議考慮第三種寫法,在代碼的簡潔性和統一管理上都要優雅很多,相比較第一種可以減少大量冗余代碼
II. 其他
1. 一灰灰 Blog: https://liuyueyi.github.io/hexblog
一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 聲明
盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰 Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
一灰灰 blog