Spring的秒表StopWatch优雅的程序计时器 -第455篇

历史文章(文章累计450+)

国内最全的Spring Boot系列之一

国内最全的Spring Boot系列之二

国内最全的Spring Boot系列之三

国内最全的Spring Boot系列之四

国内最全的Spring Boot系列之五》

走进MyBatis源码一探Spring扩展点「知识点多多」「扩展点实战系列」- 第449篇

走进SpringBoot源码吃透Spring扩展点「扩展点实战系列」- 第450篇

5个月的精华:Spring/SpringBoot扩展点手册:手册在手,编码无忧:全网独一份 - 第451篇

SpringBoot添加外部jar包及打包(亲测有效) - 第452篇

SpringBoot引入外部jar包,项目打包成war包发布(亲测有效) - 第453篇

SpringBoot中使用Spring-Retry重试框架 - 第454篇

悟纤:最近代码逻辑,添加了很多的耗时的代码,感觉写的不是很好,师傅有更好的方案吗?

师傅:这个倒是有一个秒表StopWatch,可以稍微优化代码。

悟纤:那师傅介绍一下,让徒儿也增长下功力。

师傅:要不我直接把内里传给你吧。

悟纤:那最好不过了。

师傅:你想太多了。

悟纤:看来是电视看多了,这个时代,增加内力还得靠自己,木有办法,宝宝苦,宝宝累,宝宝好难受。

师傅:这个或许是你老了之后,你值得回忆的地方。

悟纤:那也是噢~

导读

如果想知道一个方法的执行耗时时长,一般的思路是:记录开始时间,执行业务代码,记录结束时间,方法的耗时就等于=结束时间-开始时间。这种方式可以实现基本的统计需求,如果要统计各个任务的占比,那么代码的复杂度就会增加,当然你封装出来一个类专门来处理执行耗时类。

如果使用了Spring框架,那么Spring已经提供了一个秒表工具StopWatch。

一、Java原生方式

这种方式最最简单,最好理解,经常会这么来写:

public void test1() throws InterruptedException {    long startTime = System.currentTimeMillis();   //获取开始时间    //函数主体代码    //...    TimeUnit.SECONDS.sleep(1);    long endTime = System.currentTimeMillis(); //获取结束时间    System.out.println("程序运行时间: " + (endTime - startTime) + "ms");}

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

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

public void test2() throws InterruptedException {    Instant start = Instant.now();    //doSomething();    TimeUnit.SECONDS.sleep(1);    Instant end = Instant.now();    Duration duration = Duration.between(start, end);    System.out.println("程序运行时间(毫秒) = " + duration.toMillis());    System.out.println("程序运行时间(纳秒) = " + duration.toNanos());}

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

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

二、秒表StopWatch

工具类StopWatch,秒表工具,执行时间监视器,用来统计任务的耗时的工具类。

2.1 工具类提供者

不单单只有spring提供了这个工具类,apache,google也提供了:

com.google.common.base.Stopwatch;

org.apache.commons.lang3.time.StopWatch;

springframework.util.StopWatch;

2.2 工具类使用

对于Spring Watch的使用很简单,直接看下代码:

public void test3() throws InterruptedException {    StopWatch stopWatch = new StopWatch("用户注册");    //启动任务一    stopWatch.start("保存用户信息");    //执行业务逻辑    TimeUnit.SECONDS.sleep(1);    stopWatch.stop();    //启动任务二    stopWatch.start("创建用户钱包信息");    //执行业务逻辑    TimeUnit.SECONDS.sleep(2);    stopWatch.stop();    //会输出所有任务的信息    System.out.println(stopWatch.prettyPrint());    // 只输出总的:StopWatch '用户注册': running time = 3004621914 ns    //System.out.println(stopWatch.shortSummary());    // 任务总的耗时  如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用    System.out.println("所有任务总耗时(毫秒):" + stopWatch.getTotalTimeMillis());    System.out.println("任务总数:" + stopWatch.getTaskCount());    System.out.println("所有任务详情:" + stopWatch.getTaskInfo()); // 拿到所有的任务}

控制台打印:

这里的单位是ns(纳秒):

1s=1000ms(毫秒)

1ms=1000us(微妙)

1us=1000ns(纳秒)

2.3 使用场景

在一个大任务下,可能有多个小的步骤任务,而我们需要知道各个步骤任务的用时情况。

2.4 优缺点

优点:

(1)Spring自带工具类,可直接使用

(2)代码实现简单,使用更简单

(3)统一归纳,展示每项任务耗时与占用总时间的百分比,展示结果直观,性能消耗相对较小,并且最大程度的保证了start与stop之间的时间记录的准确性。

(4)可在start时直接指定任务名字,从而更加直观的显示记录结果(也可以不指定,但最好指定下,比较直观)。

对于以上的优点,我觉得最重要的是第(3)点,任务耗比以及直观的展示(prettyPrint())。

缺点:

(1)一个StopWatch实例一次只能开启一个task,不能同时start多个task,并且在该task未stop之前不能start一个新的task,必须在该task stop之后才能开启新的task,若要一次开启多个,需要new不同的StopWatch实例。

(2)代码侵入式使用,需要改动多处代码。

2.5 开发小建议

你可以使用拦截器或者过滤器,在起始的时候将StopWatch对象放入ThreadLocal中,然后封装一个工具类,就可以在业务代码的任何地方“打点”了,在过滤器或拦截器的收尾处打印计时统计。这样用来做代码性能分析应该不错。

2.6 看看源码

对于StopWatch实现起来还是很简单的,其实你自己也完全可以搞定一个。

首先定义了几个关键的变量:

看下start()方法:

看下stop()方法:

对于TaskInfo就在StopWatch类中,是一个内部静态类:

这里普及一个知识点,内部静态类,要是咱们自己实现使用内部类就可以了,这里要了解下内部静态类的一些概念,才能理解这么写的好处了。

为什么要设计Java内部类?

为什么要将一个类设计成内部类:

(1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象那个的信息相互独立;

(2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类;

(3)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

(4)方便编写事件驱动程序;

(5)方便编写线程代码。

然后我们再来说说为什么又将内部类设计为静态内部类与内部类:

(1)首先来看一下静态内部类的特点:我是静态内部类,只不过是想借你的外壳用一下。本身来说,我和你没有什么“强依赖”上的关系。没有你,我也可以创建实例。那么,在设计内部类的时候我们就可以做出权衡:如果我内部类与你外部类关系不紧密,耦合程度不高,不需要访问外部类的所有属性或方法,那么我就设计成静态内部类。而且,由于静态内部类与外部类并不会保存相互之间的引用

(2)既然上面已经说了什么时候应该用静态内部类,那么如果你的需求不符合静态内部类所提供的一切好处,你就应该考虑使用内部类了。最大的特点就是:你在内部类中需要访问有关外部类的所有属性及方法,我知晓你的一切... ...

简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着xx(不能实例化)。

那静态内部类与普通内部类有什么区别呢?

(1)静态内部类不持有外部类的引用

在普通内部类中,我们可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为内部类持有一个外部类的引用,可以自由访问。而静态内部类,则只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的),其他则不能访问。

(2)静态内部类不依赖外部类

普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,也就是说它们会同生同死,一起声明,一起被垃圾回收器回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的。

(3)普通内部类不能声明static的方法和变量

普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是final static修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。

总结

(1)可以看出StopWatch对于记录程序运行时间提供了多个api,方便按任务(比如业务A B)进行时间统计,并提供整个运行过程的概览(最后的统计部分)。总结来说我们也可以使用基础的java api封装出类似的功能,但已有轮子,就没必要重复造了。

(2)StopWatch的使用要点:使用start(taskName)开启一个任务,使用stop()结束任务。

\(^o^)/~你的小小鼓励,是博主坚持的动力,如果本文你有收获,点个赞再走呗~

悟纤:最近代码逻辑,添加了很多的耗时的代码,感觉写的不是很好,师傅有更好的方案吗?

师傅:这个倒是有一个秒表StopWatch,可以稍微优化代码。

悟纤:那师傅介绍一下,让徒儿也增长下功力。

师傅:要不我直接把内里传给你吧。

悟纤:那最好不过了。

师傅:你想太多了。

悟纤:看来是电视看多了,这个时代,增加内力还得靠自己,木有办法,宝宝苦,宝宝累,宝宝好难受。

师傅:这个或许是你老了之后,你值得回忆的地方。

悟纤:那也是噢~

导读

如果想知道一个方法的执行耗时时长,一般的思路是:记录开始时间,执行业务代码,记录结束时间,方法的耗时就等于=结束时间-开始时间。这种方式可以实现基本的统计需求,如果要统计各个任务的占比,那么代码的复杂度就会增加,当然你封装出来一个类专门来处理执行耗时类。

如果使用了Spring框架,那么Spring已经提供了一个秒表工具StopWatch。

一、Java原生方式

这种方式最最简单,最好理解,经常会这么来写:

public void test1() throws InterruptedException {    long startTime = System.currentTimeMillis();   //获取开始时间    //函数主体代码    //...    TimeUnit.SECONDS.sleep(1);    long endTime = System.currentTimeMillis(); //获取结束时间    System.out.println("程序运行时间: " + (endTime - startTime) + "ms");}

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

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

public void test2() throws InterruptedException {    Instant start = Instant.now();    //doSomething();    TimeUnit.SECONDS.sleep(1);    Instant end = Instant.now();    Duration duration = Duration.between(start, end);    System.out.println("程序运行时间(毫秒) = " + duration.toMillis());    System.out.println("程序运行时间(纳秒) = " + duration.toNanos());}

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

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

二、秒表StopWatch

工具类StopWatch,秒表工具,执行时间监视器,用来统计任务的耗时的工具类。

2.1 工具类提供者

不单单只有spring提供了这个工具类,apache,google也提供了:

com.google.common.base.Stopwatch;

org.apache.commons.lang3.time.StopWatch;

springframework.util.StopWatch;

2.2 工具类使用

对于Spring Watch的使用很简单,直接看下代码:

public void test3() throws InterruptedException {    StopWatch stopWatch = new StopWatch("用户注册");    //启动任务一    stopWatch.start("保存用户信息");    //执行业务逻辑    TimeUnit.SECONDS.sleep(1);    stopWatch.stop();    //启动任务二    stopWatch.start("创建用户钱包信息");    //执行业务逻辑    TimeUnit.SECONDS.sleep(2);    stopWatch.stop();    //会输出所有任务的信息    System.out.println(stopWatch.prettyPrint());    // 只输出总的:StopWatch '用户注册': running time = 3004621914 ns    //System.out.println(stopWatch.shortSummary());    // 任务总的耗时  如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用    System.out.println("所有任务总耗时(毫秒):" + stopWatch.getTotalTimeMillis());    System.out.println("任务总数:" + stopWatch.getTaskCount());    System.out.println("所有任务详情:" + stopWatch.getTaskInfo()); // 拿到所有的任务}

控制台打印:

这里的单位是ns(纳秒):

1s=1000ms(毫秒)

1ms=1000us(微妙)

1us=1000ns(纳秒)

2.3 使用场景

在一个大任务下,可能有多个小的步骤任务,而我们需要知道各个步骤任务的用时情况。

2.4 优缺点

优点:

(1)Spring自带工具类,可直接使用

(2)代码实现简单,使用更简单

(3)统一归纳,展示每项任务耗时与占用总时间的百分比,展示结果直观,性能消耗相对较小,并且最大程度的保证了start与stop之间的时间记录的准确性。

(4)可在start时直接指定任务名字,从而更加直观的显示记录结果(也可以不指定,但最好指定下,比较直观)。

对于以上的优点,我觉得最重要的是第(3)点,任务耗比以及直观的展示(prettyPrint())。

缺点:

(1)一个StopWatch实例一次只能开启一个task,不能同时start多个task,并且在该task未stop之前不能start一个新的task,必须在该task stop之后才能开启新的task,若要一次开启多个,需要new不同的StopWatch实例。

(2)代码侵入式使用,需要改动多处代码。

2.5 开发小建议

你可以使用拦截器或者过滤器,在起始的时候将StopWatch对象放入ThreadLocal中,然后封装一个工具类,就可以在业务代码的任何地方“打点”了,在过滤器或拦截器的收尾处打印计时统计。这样用来做代码性能分析应该不错。

2.6 看看源码

对于StopWatch实现起来还是很简单的,其实你自己也完全可以搞定一个。

首先定义了几个关键的变量:

看下start()方法:

看下stop()方法:

对于TaskInfo就在StopWatch类中,是一个内部静态类:

这里普及一个知识点,内部静态类,要是咱们自己实现使用内部类就可以了,这里要了解下内部静态类的一些概念,才能理解这么写的好处了。

为什么要设计Java内部类?

为什么要将一个类设计成内部类:

(1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象那个的信息相互独立;

(2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类;

(3)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

(4)方便编写事件驱动程序;

(5)方便编写线程代码。

然后我们再来说说为什么又将内部类设计为静态内部类与内部类:

(1)首先来看一下静态内部类的特点:我是静态内部类,只不过是想借你的外壳用一下。本身来说,我和你没有什么“强依赖”上的关系。没有你,我也可以创建实例。那么,在设计内部类的时候我们就可以做出权衡:如果我内部类与你外部类关系不紧密,耦合程度不高,不需要访问外部类的所有属性或方法,那么我就设计成静态内部类。而且,由于静态内部类与外部类并不会保存相互之间的引用

(2)既然上面已经说了什么时候应该用静态内部类,那么如果你的需求不符合静态内部类所提供的一切好处,你就应该考虑使用内部类了。最大的特点就是:你在内部类中需要访问有关外部类的所有属性及方法,我知晓你的一切... ...

简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着xx(不能实例化)。

那静态内部类与普通内部类有什么区别呢?

(1)静态内部类不持有外部类的引用

在普通内部类中,我们可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为内部类持有一个外部类的引用,可以自由访问。而静态内部类,则只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的),其他则不能访问。

(2)静态内部类不依赖外部类

普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,也就是说它们会同生同死,一起声明,一起被垃圾回收器回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的。

(3)普通内部类不能声明static的方法和变量

普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是final static修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。

总结

(1)可以看出StopWatch对于记录程序运行时间提供了多个api,方便按任务(比如业务A B)进行时间统计,并提供整个运行过程的概览(最后的统计部分)。总结来说我们也可以使用基础的java api封装出类似的功能,但已有轮子,就没必要重复造了。

(2)StopWatch的使用要点:使用start(taskName)开启一个任务,使用stop()结束任务。

\(^o^)/~你的小小鼓励,是博主坚持的动力,如果本文你有收获,点个赞再走呗~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟纤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值