相关阅读

【小家java】java5新特性(简述十大新特性) 重要一跃

【小家java】java6新特性(简述十大新特性) 鸡肋升级

【小家java】java7新特性(简述八大新特性) 不温不火

【小家java】java8新特性(简述十大新特性) 饱受赞誉

【小家java】java9新特性(简述十大新特性) 褒贬不一

【小家java】java10新特性(简述十大新特性) 小步迭代

【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本


前言

编码过程中我们经常会希望得到一段代码(一个方法)的执行时间,本文将介绍两种时间监视器(秒表)来让你优雅的、灵活的处理这个问题。

Java源生方式

这种方式最最简单,最好理解,当然也是最为常用:我们自己书写。

例如:我们如果要统计一段代码的执行时间,经常会这么来写:

    public static void main(String[] args) {
long startTime = System.currentTimeMillis(); //获取开始时间 //函数主体代码
//... long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: " + (endTime - startTime) + "ms");
}

大多数时候我们使用ms来表示即可,但是这么写缺乏灵活性。倘若我们要展示成纳秒、秒、甚至分钟,还得我们自己处理(把毫秒值拿来进行转换~ )

当然可能到了JDK8以后,我们这么做能变得稍微灵活一些:可以这么处理:

    public static void main(String[] args) {
Instant start = Instant.now();
//doSomething();
Instant end = Instant.now(); Duration duration = Duration.between(start, end);
System.out.println("millis = " + duration.toMillis());
}

这个比上面灵活度强一些。但也还是有一定的缺点:步骤稍显复杂,总体上还是不够优雅,也不是那么的灵活。

那么本文针对此问题介绍一个工具:StopWatch执行时间监视器。借助它来统计我们程序的执行时间,带给非常多的方便和优雅。

StopWatch需要依赖额外的Jar:commons-lang3或者spring-core,但因这两个Jar是Java开发中都必导的,因此依赖兼容性方面可以忽略

StopWatch有很多开源的框架都有提供类似的功能:比如Apache的commons-lang3,当然还有Spring framwork自己提供的,本文将针对此俩分别做介绍~

Commons-lang3的StopWatch

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

    public static void main(String[] args) throws Exception {
StopWatch watch = StopWatch.createStarted(); //创建后立即start,常用
//StopWatch watch = new StopWatch();
//watch.start(); Thread.sleep(1000);
System.out.println("统计从开始到现在运行时间:" + watch.getTime() + "ms"); //1000ms Thread.sleep(1000);
watch.split();
System.out.println("从start到此刻为止的时间:" + watch.getTime());
System.out.println("从开始到第一个切入点运行时间:" + watch.getSplitTime()); //2245 Thread.sleep(1000);
watch.split();
System.out.println("从开始到第二个切入点运行时间:" + watch.getSplitTime()); watch.reset(); //重置后必须使用start方法
watch.start();
Thread.sleep(1000);
System.out.println("重新开始后到当前运行时间是:" + watch.getTime()); //1000 watch.suspend(); //暂停
Thread.sleep(6000); //模拟暂停6秒钟 watch.resume(); //上面suspend,这里要想重新统计,需要恢复一下
System.out.println("恢复后执行的时间是:" + watch.getTime()); //1000 注意此时这个值还是1000 watch.stop();
System.out.println("花费的时间》》" + watch.getTime() + "ms"); //1002ms
System.out.println("花费的时间》》" + watch.getTime(TimeUnit.SECONDS) + "s"); //1s 可以直接转成s }

打印结果:

统计从开始到现在运行时间:1007ms
从start到此刻为止的时间:2008
从开始到第一个切入点运行时间:2008
从开始到第二个切入点运行时间:3009
重新开始后到当前运行时间是:1000
恢复后执行的时间是:1000
花费的时间》》1001ms
花费的时间》》1s

如上就是StopWatch的基本使用方法,足以见到了它的强大吧,当然使用起来复杂度也是提升了些的。

核心原理解释

原理相对简单,简单看看源码便知:

// @since 2.0
public class StopWatch {
// @since 3.5 这个静态方法出现得稍微晚点哦~
public static StopWatch createStarted() {
final StopWatch sw = new StopWatch();
sw.start();
return sw;
} // 这些成员变量是实现的核心~~~~~~~~~~~~~~
private State runningState = State.UNSTARTED;
private SplitState splitState = SplitState.UNSPLIT;
private long startTime;
// 思考:为何有了nonaTime这里还得记录一个Millis Time呢???
// 因为nanoTime只能拿来计算差值(耗时) 但是getStartTime()这个老API还得靠MillsTime~~~
private long startTimeMillis;
private long stopTime; // 可见:start方法可不是能够多次调用的哦~~和状态是有关的
public void start() {
if (this.runningState == State.STOPPED) {
throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
}
if (this.runningState != State.UNSTARTED) {
throw new IllegalStateException("Stopwatch already started. ");
}
this.startTime = System.nanoTime();
this.startTimeMillis = System.currentTimeMillis();
this.runningState = State.RUNNING;
} // 停表时,最重要的是记录下了stopTime 的值~~~然后标记状态
public void stop() {
if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
throw new IllegalStateException("Stopwatch is not running. ");
}
if (this.runningState == State.RUNNING) {
this.stopTime = System.nanoTime();
}
this.runningState = State.STOPPED;
} // 状态变为非开始状态...
public void reset() {
this.runningState = State.UNSTARTED;
this.splitState = SplitState.UNSPLIT;
} // 暂停:stopTime 也给了一个值
public void suspend() {
if (this.runningState != State.RUNNING) {
throw new IllegalStateException("Stopwatch must be running to suspend. ");
}
this.stopTime = System.nanoTime();
this.runningState = State.SUSPENDED;
} // 这两个方法是获取差值的
public long getTime() {
return getNanoTime() / NANO_2_MILLIS;
}
// @since 3.5
public long getTime(final TimeUnit timeUnit) {
return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
} // @since 2.4 老API 这叫获取启动的时间(啥时候启动的)
public long getStartTime() {
if (this.runningState == State.UNSTARTED) {
throw new IllegalStateException("Stopwatch has not been started");
}
// System.nanoTime is for elapsed time
return this.startTimeMillis;
}
}

可以看到原理是很简单的,无非就是包装了暂停、回复、split等功能嘛

使用细节

getTimegetSplitTime有啥区别呢?

为了说明问题,此处我们看看getNanoTime()getSplitNanoTime()亦可:

	public long getNanoTime() {
if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
return this.stopTime - this.startTime;
} else if (this.runningState == State.UNSTARTED) {
return 0;
} else if (this.runningState == State.RUNNING) {
return System.nanoTime() - this.startTime;
}
throw new RuntimeException("Illegal running state has occurred.");
} public long getSplitNanoTime() {
if (this.splitState != SplitState.SPLIT) {
throw new IllegalStateException("Stopwatch must be split to get the split time. ");
}
return this.stopTime - this.startTime;
}

我们发现:

  • 调用getSplit...相关方法前,必须先调用Split方法

spilit()方法源码如下:

	public void split() {
if (this.runningState != State.RUNNING) {
throw new IllegalStateException("Stopwatch is not running. ");
}
this.stopTime = System.nanoTime();
this.splitState = SplitState.SPLIT;
}

在调用split方法后,watch的状态改为了SPLIT且,且,且stopTime 设置为了当前时间。因此此处我们的stopTime停止了,这个时候调用getSplitNanoTime(),返回的是start到split那时的时间差值。因此用此方法可以插入先停止stopTime()(有点插队的赶脚),最后再输出(先插好队,最后在输出)~

getTime()就是拿当前的时间戳,减去startTime,一般不涉及到stopTime的值,因此splitTime处理计算时间显然更加的灵活,但是,一般我们使用getTime()就足够了

Spring的StopWatch

Spring提供的这个任务监视器,我还是蛮喜欢使用的,因为一个它能够帮我同事监控多个任务,使用起来也很方便。先看一个简单的使用案例:

注意:一个监视器能够记录多个任务的执行时间这个特点非常重要哦~

比如:我们可以记录多段代码耗时时间,然后一次性打印~

    public static void main(String[] args) throws Exception {
// 强烈每一个秒表都给一个id,这样查看日志起来能够更加的精确
// 至于Id 我觉得给UUID是可行的~
StopWatch sw = new StopWatch(UUID.randomUUID().toString()); sw.start("起床");
Thread.sleep(1000);
System.out.println("当前任务名称:" + sw.currentTaskName());
sw.stop(); sw.start("洗漱");
Thread.sleep(2000);
System.out.println("当前任务名称:" + sw.currentTaskName());
sw.stop(); sw.start("锁门");
Thread.sleep(500);
System.out.println("当前任务名称:" + sw.currentTaskName());
sw.stop(); System.out.println(sw.prettyPrint()); // 这个方法打印在我们记录日志时是非常友好的 还有百分比的分析哦
System.out.println(sw.shortSummary());
System.out.println(sw.currentTaskName()); // stop后它的值为null // 最后一个任务的相关信息
System.out.println(sw.getLastTaskName());
System.out.println(sw.getLastTaskInfo()); // 任务总的耗时 如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用
System.out.println("所有任务总耗时:" + sw.getTotalTimeMillis());
System.out.println("任务总数:" + sw.getTaskCount());
System.out.println("所有任务详情:" + sw.getTaskInfo()); // 拿到所有的任务
}

打印:

当前任务名称:起床
当前任务名称:洗漱
当前任务名称:锁门
StopWatch 'd6ba9412-d551-4ba7-8b0e-1b7ccb42855d': running time (millis) = 3504
-----------------------------------------
ms % Task name
-----------------------------------------
01001 029% 起床
02000 057% 洗漱
00503 014% 锁门 StopWatch 'd6ba9412-d551-4ba7-8b0e-1b7ccb42855d': running time (millis) = 3504
null
锁门
org.springframework.util.StopWatch$TaskInfo@2d554825
所有任务总耗时:3504
任务总数:3
所有任务详情:[Lorg.springframework.util.StopWatch$TaskInfo;@68837a77

我个人偏爱使用Spring提供的这个监视器,是因为它提供的prettyPrint()打印在日志里进行分析可以非常的直观,并且我觉得提供的多任务支持也更加实用一点,当然仅仅个人偏好而已~

最后

很多时候,写代码也是一种艺术,而借助这种实用工具我就觉得艺术感更强些。希望我们能有追求更加美好事物的心,这点对于接纳新知识特别重要。此处推荐这个监视器来代替之前的的使用,能让小伙伴们更加灵活的分析你的代码~

知识交流

若文章格式混乱,可点击原文链接-原文链接-原文链接-原文链接-原文链接

The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群

若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群

Apache和Spring提供的StopWatch执行时间监视器的更多相关文章

  1. Spring提供的用于访问Rest服务的客户端:RestTemplate实践

    什么是RestTemplate? RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效 ...

  2. Spring提供的iBatis的SqlMap配置

    1.    applicationContext.xml <!-- Spring提供的iBatis的SqlMap配置--> <bean id="sqlMapClient&q ...

  3. (转)Spring提供的CharacterEncoding和OpenSessionInView功能

    http://blog.csdn.net/yerenyuan_pku/article/details/52902282 前面我们以一种更加优雅的方式集成了Spring4.2.5+Hibernate4. ...

  4. 使用 Spring 提供的 restTemplate 完成 Http 服务消费

    RestTemplate 介绍 RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高 ...

  5. spring提供的解决中文乱码方案

    在表单提交时,如果遇到中文符号会出现乱码问题. Spring提供一个CharacterEncodingFilter过滤器,可用于解决乱码问题. CharacterEncodingFilter使用的时候 ...

  6. 开涛spring3(7.4) - 对JDBC的支持 之 7.4 Spring提供的其它帮助

    7.4  Spring提供的其它帮助 7.4.1  SimpleJdbc方式 Spring JDBC抽象框架提供SimpleJdbcInsert和SimpleJdbcCall类,这两个类通过利用JDB ...

  7. 使用Spring提供的缓存抽象机制整合EHCache为项目提供二级缓存

      Spring自身并没有实现缓存解决方案,但是对缓存管理功能提供了声明式的支持,能够与多种流行的缓存实现进行集成. Spring Cache是作用在方法上的(不能理解为只注解在方法上),其核心思想是 ...

  8. 8 -- 深入使用Spring -- 8...1 Spring提供的DAO支持

    8.8.1 Spring提供的DAO支持. DAO模式是一种标准的Java EE设计模式,DAO模式的核心思想是,所有的数据库访问都通过DAO组件完成,DAO组件封装了数据库的增.删.查.改等原子操作 ...

  9. 问题排查之'org.apache.rocketmq.spring.starter.core.RocketMQTemplate' that could not be found.- Bean method 'rocketMQTemplate' in 'RocketMQAutoConfiguration' not loaded.

    背景 今天将一个SpringBoot项目的配置参数从原有的.yml文件迁移到Apollo后,启动报错“Bean method 'rocketMQTemplate' in 'RocketMQAutoCo ...

随机推荐

  1. Spring之bean后处理器

    Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法.由于该Bean是由其它Bean自动调用执行,不是程序员手工调用,故此Bean无须id属性.需要做的是, ...

  2. 开源|性能优化利器:数据库审核平台Themis的选型与实践

    作者:韩锋 出处:DBAplus社群分享:来源:宜信技术学院 Themis开源地址:https://github.com/CreditEaseDBA 一.面临的挑战 1.运维规模及种类 我相信,这也是 ...

  3. kubernetes使用http rest api访问集群之使用postman工具访问 apiserver

    系列目录 前面一节我们介绍了使用curl命令行工具访问apiserver,命令行工具快速高效,但是对于输出非常长的内容查看不是特别方便,尤其终端界面输入的东西非常多的时候,过长的内容不是特别容易通过滚 ...

  4. 深入理解Java虚拟机(字节码执行引擎)

    深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...

  5. 使用wireshark捕获SSL/TLS包并分析

    原创博客,转载请注出处! TLS运作方式如下图:

  6. kubernetes实战篇之通过api-server访问dashboard

    系列目录 前面一节我们介绍了如何使用kube-proxy搭建代理访问dashboard,这样做缺点非常明显,第一可以通过http访问,第二是这种方式要启动一个后台进程,如果进程关闭了则不能够访问了,还 ...

  7. linux下svn安装

    1.环境centos6.4 2.安装svnyum -y install subversion 3.配置 建立版本库目录mkdir /www/svndata svnserve -d -r /www/sv ...

  8. Python笔记【4】_字典学习

    #!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 ''' dict:字典以“{}”包围,以“键:值 ...

  9. JavaScript 之有趣的函数(函数声明、调用、预解析、作用域)

    前言:“函数是对象,函数名是指针.”,函数名仅仅是指向函数的指针,与其他包含函数指针的变量没有什么区别,话句话说,一个函数可能有多个名字. -1.函数声明,function+函数名称.调用方法:函数名 ...

  10. restapi(0)- 平台数据维护,写在前面

    在云计算的推动下,软件系统发展趋于平台化.云平台系统一般都是分布式的集群系统,采用大数据技术.在这方面akka提供了比较完整的开发技术支持.我在上一个系列有关CQRS的博客中按照实际应用的要求对akk ...