如何优雅的在java中统计代码块耗时

简介: 在我们的实际开发中,多多少少会遇到统计一段代码片段的耗时的情况,我们一般的写法如下

在我们的实际开发中,多多少少会遇到统计一段代码片段的耗时的情况,我们一般的写法如下


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()) {
    ...
}
复制代码


优点是:简单,适用范围广泛,且适合统一管理;缺点是依然有代码侵入


说明


上面第二种方法看着属于最优雅的方式,但是限制性强;如果有更灵活的需求,建议考虑第三种写法,在代码的简洁性和统一管理上都要优雅很多,相比较第一种可以减少大量冗余代码




相关文章
|
2月前
|
Arthas Java 中间件
如何优雅的在java中统计代码块耗时
如何优雅的在java中统计代码块耗时
41 1
|
4月前
|
人工智能 算法 Java
第十三届蓝桥杯B组Java(试题C:字符统计)
第十三届蓝桥杯B组Java(试题C:字符统计)
55 0
|
2月前
|
Java
java中你知道这四种代码块吗?
java中你知道这四种代码块吗?
11 0
|
2月前
|
Java
java中你知道的这四种代码块吗?
java中你知道的这四种代码块吗?
12 0
java中你知道的这四种代码块吗?
|
2月前
|
存储 Java API
Java统计功能
Java统计功能
15 0
|
3月前
|
Java
java字符串练习题4、统计一行字符串中所有的字符类型数量
java字符串练习题4、统计一行字符串中所有的字符类型数量
31 0
|
4月前
|
Java C++ Python
Rust每日一练(Leetday0016) 全排列I\II、旋转图像
Rust每日一练(Leetday0016) 全排列I\II、旋转图像
22 0
Rust每日一练(Leetday0016) 全排列I\II、旋转图像
|
4月前
|
算法 C++ Java
Java每日一练(20230423) 数组元素统计、杨辉三角II、二进制求和
Java每日一练(20230423) 数组元素统计、杨辉三角II、二进制求和
32 0
Java每日一练(20230423) 数组元素统计、杨辉三角II、二进制求和
|
4月前
|
Java API Scala
【Flink】Flink Java 统计词频 开发
【1月更文挑战第26天】【Flink】Flink Java 统计词频 开发
|
4月前
|
Java
【JAVA杂货铺】一文带你走进面向对象编程|构造方法调用 | 代码块分类| 期末复习系列 | (中3)
【JAVA杂货铺】一文带你走进面向对象编程|构造方法调用 | 代码块分类| 期末复习系列 | (中3)
18 0