[Java/时间] 耗时计算与耗时观测

目录

需求描述

  • 针对应用程序的性能优化工作,观察程序内部各处理阶段耗时,一直是一个基础操作。

  • 在之前的工作中,通常会选择使用下面这种方法来统计耗时:

public static void main(String[] args) throws InterruptedException {
    long startTime = System.currentTimeMillis();//获取毫秒级时间戳
	
    Thread.sleep(1000);
    long endTime = System.currentTimeMillis();
    System.out.println("耗时:" + (endTime - startTime) + "ms");
}
  • 弊端:
  • 代码臃肿、繁琐。在需要统计耗时的点较多的时候,这种方式会显得有点繁琐,经过一些调研之后发现,可以使用StopWatch来处理统计代码中的耗时操作。
  • System.currentTimeMillis() : 毫秒级时间戳
  • System.currentTimeMillis() : 纳秒级时间数(无法直接转时间戳、时间字符串,需要进行换算)
  • 那么,应该是有相关的第三方开源库才对。

Java应用程序的耗时计算库

System.currentTimeMillis() : JDK内置方法

参见: 需求描述章节

  • currentTimeMillis/毫秒
public class TimeIntervalTest {
    public static void main(String[] args) throws InterruptedException {
        // 开始时间
        long stime = System.currentTimeMillis();
        // 执行时间(1s)
        Thread.sleep(1000);
        // 结束时间
        long etime = System.currentTimeMillis();
        // 计算执行时间
        System.out.printf("执行时长:%d 毫秒.", (etime - stime));
    }
}

System.nanoTime() : JDK内置方法

  • nanotTime/纳秒
public class TimeIntervalTest {
    public static void main(String[] args) throws InterruptedException {
        // 开始时间
        long stime = System.nanoTime();
        // 执行时间(1s)
        Thread.sleep(1000);
        // 结束时间
        long etime = System.nanoTime();
        // 计算执行时间
        System.out.printf("执行时长:%d 纳秒.", (etime - stime));
    }
}

Date#getTime() : JDK 内置方法

import java.util.Date;
 
public class TimeIntervalTest {
    public static void main(String[] args) throws InterruptedException {
        // 开始时间
        Date sdate = new Date();
        // 执行时间(1s)
        Thread.sleep(1000);
        // 结束时间
        Date edate = new Date();
        //  统计执行时间(毫秒)
        System.out.printf("执行时长:%d 毫秒." , (edate.getTime() - sdate.getTime())); 
    }
}

StopWatch : Spring 框架

import org.springframework.util.StopWatch;


StopWatch stopWatch = new StopWatch();
// 开始时间
stopWatch.start();


// 执行时间(1s)
Thread.sleep(1000);

// 结束时间
stopWatch.stop();
// 统计执行时间(秒)
System.out.printf("执行时长:%d 秒.%n", stopWatch.getTotalTimeSeconds()); // %n 为换行
// 统计执行时间(毫秒)
System.out.printf("执行时长:%d 毫秒.%n", stopWatch.getTotalTimeMillis()); 
// 统计执行时间(纳秒)
System.out.printf("执行时长:%d 纳秒.%n", stopWatch.getTotalTimeNanos());

以上程序的执行结果为:

执行时长:0.9996313 秒. 执行时长:999 毫秒. 执行时长:999631300 纳秒.

注:Thread#sleep 方法的执行时间稍有偏差,在 1s 左右都是正常的。

StopWatch : Guava 框架

引入依赖

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>29.0-jre</version>
</dependency>

案例示范

import com.google.common.base.Stopwatch;
 
import java.util.concurrent.TimeUnit;
 
public class TimeIntervalTest {
    public static void main(String[] args) throws InterruptedException {
        // 创建并启动计时器
        Stopwatch stopwatch = Stopwatch.createStarted();
        // 执行时间(1s)
        Thread.sleep(1000);
        // 停止计时器
        stopwatch.stop();
        // 执行时间(单位:秒)
        System.out.printf("执行时长:%d 秒. %n", stopwatch.elapsed().getSeconds()); // %n 为换行
        // 执行时间(单位:毫秒)
        System.out.printf("执行时长:%d 豪秒.", stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }
}

StopWatch : common.lang3 版

依赖

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.9</version>
  <scope>compile</scope>
</dependency>

核心API: org.apache.commons.lang3.time.StopWatch(秒表/计时器类)

案例示范

    public static void main(String[] args) throws InterruptedException {
        //创建1个秒表实例对象
        StopWatch stopWatch = new StopWatch();
        // 开始计时
        stopWatch.start();

        // 执行时间(1s)
        Thread.sleep(1000);

        // 中间阶段,计1次
        stopWatch.split();

        System.out.println("step1-1 执行时长:" + stopWatch.getTime() + " 毫秒.");
        System.out.println("step1-2 执行时长:" + stopWatch.getTime(TimeUnit.SECONDS) + " 秒.");
        System.out.println("step1-3 执行时长:" + stopWatch.getTime(TimeUnit.MILLISECONDS) + " 毫秒.");
        System.out.println("step1-4 执行时长:" + stopWatch.getTime(TimeUnit.NANOSECONDS) + " 纳秒.");

        System.out.println("step1-5 执行时长:" + stopWatch.getSplitTime() + " 毫秒.");//瞬间计时,推荐
        System.out.println("step1-6 执行时长:" + stopWatch.getSplitNanoTime() + " 纳秒.");//瞬间计时,推荐

        // 结束计时
        Thread.sleep(2000);
        System.out.println("step2-1 执行时长:" + stopWatch.getTime() + " 毫秒.");
        System.out.println("step2-2 执行时长:" + stopWatch.getTime(TimeUnit.SECONDS) + " 秒.");
        System.out.println("step2-3 执行时长:" + stopWatch.getTime(TimeUnit.MILLISECONDS) + " 毫秒.");
        System.out.println("step2-4 执行时长:" + stopWatch.getTime(TimeUnit.NANOSECONDS) + " 纳秒.");

        System.out.println("step2-5 执行时长:" + stopWatch.getSplitTime() + " 毫秒.");//瞬间计时,推荐
        System.out.println("step2-6 执行时长:" + stopWatch.getSplitNanoTime() + " 纳秒.");//瞬间计时,推荐

        System.out.println("startTime :" + stopWatch.getStartTime() + " 时间戳(ms)");
    }
  • out
step1-1 执行时长:1007 毫秒.
step1-2 执行时长:1 秒.
step1-3 执行时长:1007 毫秒.
step1-4 执行时长:1007450900 纳秒.
step1-5 执行时长:1006 毫秒.
step1-6 执行时长:1006983200 纳秒.


step2-1 执行时长:3019 毫秒.
step2-2 执行时长:3 秒.
step2-3 执行时长:3019 毫秒.
step2-4 执行时长:3020120600 纳秒.
step2-5 执行时长:1006 毫秒.
step2-6 执行时长:1006983200 纳秒.
startTime :1733987201826 时间戳(ms)
  • 注意事项
  • 如果要使用这个工具的话,需要注意split后并不是计算的两个阶段之间的耗时,而是从start()到当前阶段的耗时。
  • 如果想要计算两个阶段之间的耗时的话,可以写一个新的StopWatch来继承Apache的StopWatch,并拓展对应方法。

StopWatch : hutool 框架

  • HuTool中的StopWatch使用方式与Apache中的大同小异,不过它实现了阶段之间的计数功能

引入依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.31</version>
</dependency>

案例示范

  • 源码
import cn.hutool.core.date.StopWatch;
import java.util.concurrent.TimeUnit;

/**
 * 耗时监控
 *
 * @author liushuang
 * @date 2024/8/23 14:54
 */
public class TimeCost {

    public static void main(String[] args) throws InterruptedException {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start("第一阶段");
        Thread.sleep(1000);
        stopWatch.stop();

        stopWatch.start("第二阶段");
        Thread.sleep(2000);
        stopWatch.stop();

        stopWatch.start("第三阶段");
        Thread.sleep(500);
        stopWatch.stop();

        System.out.println(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
    }
}

Apache Common Lang3不同的是,这里的start可以定义描述,stop表示的是当前Task停止,所以在每个阶段都定义了一个start()和一个stop()
最后通过prettyPrint打印出监控信息,可以传入一个TimeUnit,在结果中就会按这个时间单位打印,最终得到的结果如下:

  • log

下面打印出了每个阶段的耗时,及其实践占用的百分比。

StopWatch '': running time = 3516 ms
---------------------------------------------
ms         %     Task name
---------------------------------------------
000001004  29%   第一阶段
000002004  57%   第二阶段
000000507  14%   第三阶段

有了这个结果之后,就可以定位到相对比较耗时的代码的位置,做出针对性的优化。

X 参考文献

posted @   千千寰宇  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
历史上的今天:
2017-12-12 Linux之备份(tar)/解压与压缩(gzip,bzip2,xz)【待完善】
2017-12-12 [C++]Linux之进程间通信小结【待完善】
点击右上角即可分享
微信分享提示