StopWatch的使用,替换System.currentTimeMillis()

本文介绍了如何在开发中利用Spring的StopWatch和Apache Commons Lang的StopWatch类来轻松监测代码执行时间,包括它们的用法、源码解析和注意事项。通过StopWatch,开发者可以优雅地跟踪任务间的时间消耗并生成详细的日志输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:
有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,一般我们检测某段代码执行的时间,都是以如下方式来进行的:


public static void main(String[] args) {
  Long startTime = System.currentTimeMillis();
  // 你的业务代码
  Long endTime = System.currentTimeMillis();
  Long elapsedTime = (endTime - startTime) / 1000;
  System.out.println("该段总共耗时:" + elapsedTime + "s");
}

事实上该方法通过获取执行完成时间与执行开始时间的差值得到程序的执行时间,简单直接有效,但想必写多了也是比较烦人的,尤其是碰到不可描述的代码时,会更加的让人忍不住多写几个bug聊表敬意,而且如果想对执行的时间做进一步控制,则需要在程序中很多地方修改。

此时会想是否有一个工具类,提供了这些方法,刚好可以满足这种场景?

我们可以利用已有的工具类中的秒表,常见的秒表工具类

有 org.springframework.util.StopWatch、org.apache.commons.lang.time.StopWatch以及谷歌提供的guava中的秒表(这个我没怎么用过)

spring 用法

StopWatch 是位于 org.springframework.util 包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块。简单总结一句,Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。

也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。

想要使用它,首先你需要在你的 Maven 中引入 Spring 核心包,当然 Spring MVC 和 Spring Boot 都已经自动引入了该包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>

对一切事物的认知,都是从使用开始,那就先来看看它的用法,会如下所示:


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

    // 任务一模拟休眠3秒钟
    stopWatch.start("TaskOneName");
    Thread.sleep(1000 * 3);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskTwoName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskThreeName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 打印出耗时
    System.out.println(stopWatch.prettyPrint());
    System.out.println(stopWatch.shortSummary());
    // stop后它的值为null
    System.out.println(stopWatch.currentTaskName()); 
    
    // 最后一个任务的相关信息
    System.out.println(stopWatch.getLastTaskName());
    System.out.println(stopWatch.getLastTaskInfo());
    
    // 任务总的耗时  如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用
    System.out.println("所有任务总耗时:" + sw.getTotalTimeMillis());
    System.out.println("任务总数:" + sw.getTaskCount());
    System.out.println("所有任务详情:" + sw.getTaskInfo());
}

如图所示,StopWatch 不仅正确记录了上个任务的执行时间,并且在最后还可以给出精确的任务执行时间(纳秒级别)和耗时占比,这或许就会比我们自己输出要优雅那么一些。
2.2 源码
老规矩,由浅入深。看完用法,我们来看看源码。先看下组成 StopWatch 的属性


public class StopWatch {
    /**
  * 本实例的唯一 Id,用于在日志或控制台输出时区分的。
  */
    private final String id;
    /**
  * 是否保持一个 taskList 链表
  * 每次停止计时时,会将当前任务放入这个链表,用以记录任务链路和计时分析
  */
 private boolean keepTaskList = true;
   /**
  * 任务链表
  * 用来存储每个task的信息, taskInfo由taskName 和 totoalTime组成
  */
    private final List<StopWatch.TaskInfo> taskList;
    /**
  * 当前任务的开始时间
  */
    private long startTimeMillis;
    /**
  * 
  */
    private boolean running;
    /**
  * 当前任务名称
  */
    private String currentTaskName;
    /**
  * 最后一个任务的信息
  */
    private StopWatch.TaskInfo lastTaskInfo;
    /**
  * 任务总数
  */
    private int taskCount;
    /**
  * 程序执行时间
  */
    private long totalTimeMillis;
    ...
}

接下来,我们看一下StopWatch类的构造器和一些关键方法
在这里插入图片描述
2.3 注意事项

  1. StopWatch对象不是设计为线程安全的,并且不使用同步。
  2. 一个StopWatch实例一次只能开启一个task,不能同时start多个task
  3. 在该task还没stop之前不能start一个新的task,必须在该task stop之后才能开启新的task
  4. 若要一次开启多个,需要new不同的StopWatch实例

apache 用法

StopWath是 apache commons lang3 包下的一个任务执行时间监视器,与我们平时常用的秒表的行为比较类似,我们先看一下其中的一些重要方法:
在这里插入图片描述

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

Apache提供的这个任务执行监视器功能丰富强大,灵活性强,如下经典实用案例:


public static void main(String[] args) throws InterruptedException {
    //创建后立即start,常用
    StopWatch watch = StopWatch.createStarted();

    // StopWatch watch = new StopWatch();
    // watch.start();

    Thread.sleep(1000);
    System.out.println(watch.getTime());
    System.out.println("统计从开始到现在运行时间:" + watch.getTime() + "ms");

    Thread.sleep(1000);
    watch.split();
    System.out.println("从start到此刻为止的时间:" + watch.getTime());
    System.out.println("从开始到第一个切入点运行时间:" + watch.getSplitTime());
    Thread.sleep(1000);
    watch.split();
    System.out.println("从开始到第二个切入点运行时间:" + watch.getSplitTime());

    // 复位后, 重新计时
    watch.reset();
    watch.start();
    Thread.sleep(1000);
    System.out.println("重新开始后到当前运行时间是:" + watch.getTime());

    // 暂停 与 恢复
    watch.suspend();
    System.out.println("暂停2秒钟");
    Thread.sleep(2000);

    // 上面suspend,这里要想重新统计,需要恢复一下
    watch.resume();
    System.out.println("恢复后执行的时间是:" + watch.getTime());

    Thread.sleep(1000);
    watch.stop();

    System.out.println("花费的时间》》" + watch.getTime() + "ms");
    // 直接转成s
    System.out.println("花费的时间》》" + watch.getTime(TimeUnit.SECONDS) + "s");
}

结束

### Java 中 `System.currentTimeMillis()` 的不准原因 `System.currentTimeMillis()` 方法返回的是自1970年1月1日午夜(UTC时间)以来的毫秒数。然而,在某些情况下,它可能无法提供精确的结果。主要原因如下: #### 1. **依赖于操作系统时钟** 该方法基于操作系统的系统时钟,而系统时钟可能会受到多种因素的影响,例如手动调整、自动同步(NTP)、节能模式下的时钟漂移等[^2]。 #### 2. **分辨率较低** 在一些旧的操作系统或硬件上,`System.currentTimeMillis()` 的分辨率可能仅为几十毫秒甚至更高,这意味着两次调用之间的时间间隔小于其分辨率时,得到的结果可能是相同的[^1]。 --- ### 解决方案 为了获得更精确的时间测量,可以采用以下替代方案之一: #### 使用 `System.nanoTime()` 对于高精度的时间测量需求,推荐使用 `System.nanoTime()` 替代 `System.currentTimeMillis()`。`nanoTime()` 提供纳秒级的时间测量能力,并且不受系统时钟更改的影响。需要注意的是,`nanoTime()` 返回的值仅适用于计算时间差,而不表示实际的 wall-clock 时间。 以下是使用 `System.nanoTime()` 测量一段代码执行时间的示例: ```java long start = System.nanoTime(); // 执行某段代码 long end = System.nanoTime(); long duration = (end - start) / 1_000_000; // 将结果转换为毫秒 System.out.println("Code execution time: " + duration + " ms"); ``` #### 高精度计时器库 如果项目允许引入外部依赖,可以选择使用成熟的第三方库来处理时间测量问题。例如,Google Guava 库中的 `Stopwatch` 类提供了简单易用的 API 来实现精准计时。 示例代码如下: ```java import com.google.common.base.Stopwatch; Stopwatch stopwatch = Stopwatch.createStarted(); // 执行某段代码 stopwatch.stop(); System.out.println("Elapsed time: " + stopwatch.elapsed().toMillis() + " ms"); ``` #### NTP 同步 当需要获取绝对时间戳并确保跨设备一致性时,可以通过网络时间协议(NTP)服务器校准时钟。Java 可以通过 HTTP 请求或其他方式连接到公共 NTP 服务器以获取标准时间。 --- ### Android 开发中的特殊场景 在 Android 平台中,由于设备多样性以及底层实现差异,单纯依靠 `System.currentTimeMillis()` 很难满足高性能应用的需求。特别是在涉及多线程并发、定时任务调度或者实时数据传输的情况下,建议优先选用 `System.nanoTime()` 或者其他专门设计的计时工具[^3]。 此外,针对分布式环境下的流量控制和服务限流等问题,除了客户端层面优化外,还需要结合服务端策略共同作用才能达到理想效果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值