Java计时新姿势StopWatch的使用方法详解
作者:chx君 发布时间:2022-01-28 00:49:57
一、背景
有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,一般我们检测某段代码执行的时间,都是以如下方式来进行的:
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、Apache的使用
二、spring 用法
2.1 初遇
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类的构造器和一些关键方法
方法 | 说明 |
---|---|
new StopWatch() | 构建一个新的秒表,不开始任何任务。 |
new StopWatch(String id) | 创建一个指定了id的StopWatch |
String getId() | 返回此秒表的ID |
void start(String taskName) | 不传入参数,开始一个无名称的任务的计时。 传入String类型的参数来开始指定任务名的任务计时 |
void stop() | 停止当前任务的计时 |
boolean isRunning() | 是否正在计时某任务 |
String currentTaskName() | 当前正在运行的任务的名称(如果有) |
long getTotalTimeMillis() | 所有任务的总体执行时间(毫秒单位) |
double getTotalTimeSeconds() | 所有任务的总时间(以秒为单位) |
String getLastTaskName() | 上一个任务的名称 |
long getLastTaskTimeMillis() | 上一个任务的耗时(毫秒单位) |
int getTaskCount() | 定时任务的数量 |
String shortSummary() | 总运行时间的简短描述 |
String prettyPrint() | 优美地打印所有任务的详细耗时情况 |
2.3 注意事项
StopWatch对象不是设计为线程安全的,并且不使用同步。
一个StopWatch实例一次只能开启一个task,不能同时start多个task
在该task还没stop之前不能start一个新的task,必须在该task stop之后才能开启新的task
若要一次开启多个,需要new不同的StopWatch实例
三、apache 用法
StopWath是 apache commons lang3 包下的一个任务执行时间监视器,与我们平时常用的秒表的行为比较类似,我们先看一下其中的一些重要方法:
方法 | 说明 |
---|---|
new StopWatch() | 构建一个新的秒表,不开始任何任务。 |
static StopWatch createStarted() | |
void start() | 开始计时 |
void stop() | 停止当前任务的计时 |
void reset() | 重置计时 |
void split() | 设置split点 |
void unsplit() | |
void suspend() | 暂停计时, 直到调用resume()后才恢复计时 |
void resume() | 恢复计时 |
long getTime() | 统计从start到现在的计时 |
long getTime(final TimeUnit timeUnit) | |
long getNanoTime() | |
long getSplitTime() | 获取从start 到 最后一次split的时间 |
long getSplitNanoTime() | |
long getStartTime() | |
boolean isStarted() | |
boolean isSuspended() | |
boolean isStopped() |
<!-- 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 中使用StopWatch来计算时间差
以前在进行时间耗时时我们通常的做法是先给出计算前后两个的时间值,然后通过详见来计算耗时时长。
eg:
long start = System.currentTimeMillis();
......业务处理
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
我们可以使用已有的工具类中的秒表来替代上述的使用方式,现有的秒表工具类有org.springframework.util.StopWatch、org.apache.commons.lang.time.StopWatch,这里以Spring的StopWatch类为例:
public static void main(String[] args) throws InterruptedException{
StopWatch stopWatch = new StopWatch("任务耗时秒表工具");
stopWatch.start("task1");
Thread.sleep(1000);
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
stopWatch.start("task2");
Thread.sleep(3000);
stopWatch.stop();
//所有任务耗时时间
System.out.println(stopWatch.getTotalTimeMillis());
System.out.println(stopWatch.prettyPrint());
StopWatch stopWatch2 = new StopWatch("任务耗时秒表工具2");
stopWatch2.start("task3");
Thread.sleep(3000);
stopWatch2.stop();
//所有任务耗时时间
System.out.println(stopWatch2.getTotalTimeMillis());
System.out.println(stopWatch2.prettyPrint());
}
五、最后
很多时候,写代码也是一种艺术,而借助这种实用工具我就觉得艺术感更强些。希望我们能有追求更加美好事物的心,这点对于接纳新知识特别重要。此处推荐这个监视器来代替之前的的使用,能让小伙伴们更加灵活的分析你的代码~
来源:https://juejin.cn/post/7049246477616414727


猜你喜欢
- 介绍最近项目开发中用到了WebView播放视频的功能,总结了开发中犯过的错误,这些错误在开发是及容易遇到的,所以我这里总结了一下,希望大家看
- 单个和批量定义别名typeAliases使用Mybatis的别名typeAliases可以在xml文件里非常方便的使用类,而不需要写出这个类
- 简介Log4J 是 Apache 的一个开源项目(官网 http://jakarta.apache.org/log4j)
- 一、Service简介Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不过它没有U
- 究其根本,原因在于,能在unity面板上识别的变量,public不是唯一的条件,另外一个条件是可序列化比如你声明了如下一个类using Sy
- 本文实例讲述了JAVA线程池原理。分享给大家供大家参考,具体如下:线程池的优点1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每
- 随着时间的推移现在的软件要求显示的内容越来越多,所以要在小的屏幕上能够更好的显示更多的内容,首先我们会想到底部菜单栏,但是有时候像今日头条新
- 我想每个写项目的人,都肯定会遇到控制权限这个问题.例如这个这个链接只能管理员访问,那个链接丫只能超级管理员访问等等,实现方式也有多种多样,控
- 关于Path之前写的也很多了,例如path绘制线,path绘制一阶,二阶和三阶贝塞尔路径,这些都是path的基本用法。今天我要带大家看的是P
- springboot 中各种配置项纪录1. @Value最早获取配置文件中的配置的时候,使用的就是这个注解,SpEL表达式语言。// 使用起
- 小菜今年计算机专业大四了,学了不少软件开发方面的东西,也学着编了些小程序,踌躇满志,一心要找一个好单位。当投递了无数份简历后,终于收到了一个
- 用函数指针变量调用函数指针变量也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数入口地址就称为函数的指针。可以用一个指针变量
- 上一篇中我们介绍了自定义实现BaseAdapter的普通实现布局,然而上一章也说了普通实现的方式效率会很低,而且对系统开销也很大,所以,那样
- 本文实例为大家分享了WPF实现背景灯光随鼠标闪动的具体代码,供大家参考,具体内容如下实现效果如下:思路:将容器分割成组合三角形Path,鼠标
- gateway网关与前端请求的跨域问题最近因项目需要,引入了gateway网关。可是发现将前端请求的端口指向网关后,用postman发送请求
- 推荐激活教程IntelliJ IDEA 2020最新激活码(亲测有效,可激活至 2089 年)最新idea2021注册码永久激活(激活到21
- 本文实例讲述了C#中foreach原理以及模拟的实现方法,分享给大家供大家参考。具体如下:public class Person:IEnum
- 整理文档,搜刮出一个SpringMvc MultipartFile实现图片文件上传示例,稍微整理精简一下做下分享。spring-servle
- 前言在mybatis和mybatis plus里,如果你的实体字段是一个枚举类型,而在数据表里是整型,这时在存储时需要进行处理,默认情况下,
- C#是一种类型安全的编程语言(所有表达式都能解析成某个类型的实例,在编译器生成的代码中,只会执行对这个类型有效的操作),和非类型安全的语言相