历史文章(文章累计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^)/~你的小小鼓励,是博主坚持的动力,如果本文你有收获,点个赞再走呗~