• Apache和Spring提供的StopWatch执行时间监视器


    相关阅读

    【小家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入群" 字样,会手动邀请入群

  • 相关阅读:
    数据库悲观锁与乐观锁
    windows消息大全
    Ubuntu 16.04 LTS GNOME版本下载
    Spring MVC-表单(Form)处理示例(转载实践)
    Spring MVC-Hello World示例(转载实践)
    Spring MVC-环境设置(转载实践)
    MyBatis在注解上使用动态SQL(@select使用if)
    Ubuntu 16.04安装Ubuntu After Install工具实现常用软件批量安装
    Ubuntu 16.04开机自动挂载硬盘分区(转)
    Spring MVC-概述(转载实践)
  • 原文地址:https://www.cnblogs.com/yourbatman/p/11261503.html
Copyright © 2020-2023  润新知