文章目录

前言

在Spring中,为了统计任务的执行时间,开发了一个轻量级的计时器,它就是:StopWatch。今天就站在用户的角度,学会让StopWatch为我们所用,顺便看看一部分源码。

SpringBoot Version:2.1.7

SpringBoot启动使用StopWatch

早在之前的博客中,就提到了springboot启动流程中用到了计时器,现在再去瞧一瞧:
org.springframework.boot.SpringApplication#run(java.lang.String...)

	public ConfigurableApplicationContext run(String... args) {
                // 构造StopWatch 
		StopWatch stopWatch = new StopWatch();
                // 开启计时
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
                        // 停止计时
			stopWatch.stop();

嗯,看上去似乎很简单呢,但在自己用的时候我们往往还要考虑跟多问题,比如:计时器到底是不是线程安全的呢?计时结束后的时间又是怎么记录的呢?用StopWatch和直接调用System接口获取时间做加减又有什么不同呢?

带着这些问题,我们钻进源码里瞧瞧。

跟进部分源码

StopWatch属性

        /** 计时器id */
	private final String id;
        /** 是否保存任务信息 */
	private boolean keepTaskList = true;
        /** 保存的任务信息 */
	private final List<TaskInfo> taskList = new LinkedList<>();

	/** 任务开始时间 */
	private long startTimeMillis;

	/** 当前执行任务名字 */
	@Nullable
	private String currentTaskName;

        /** 最近一次已完成任务的信息 **/
	@Nullable
	private TaskInfo lastTaskInfo;

        /** 计时器一共计时完成多少次 */
	private int taskCount;

	/** 计时器所有任务总耗时 */
	private long totalTimeMillis;

start方法

org.springframework.util.StopWatch#start(java.lang.String)

	public void start(String taskName) throws IllegalStateException {
                // 可见一个计时器是不能并发执行的
		if (this.currentTaskName != null) {
			throw new IllegalStateException("Can't start StopWatch: it's already running");
		}
                // 记录任务名,默认为"",可自定义
		this.currentTaskName = taskName;
                // 记录开始时间
		this.startTimeMillis = System.currentTimeMillis();
	}

stop方法

	public void stop() throws IllegalStateException {
                // 若当前没任务,则抛出异常
		if (this.currentTaskName == null) {
			throw new IllegalStateException("Can't stop StopWatch: it's not running");
		}
                // 记录结束时间
		long lastTime = System.currentTimeMillis() - this.startTimeMillis;
                // 累加总时间
		this.totalTimeMillis += lastTime;
                // 记录最近一次任务的计时信息
		this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
                // 如果为true则保存任务信息到List中
		if (this.keepTaskList) {
			this.taskList.add(this.lastTaskInfo);
		}
                // 计数器累加
		++this.taskCount;
                // 清空当前任务
		this.currentTaskName = null;
	}

其它接口不多说,有兴趣的自己看看,都非常简单。

实验Demo:

比起看源码,我们更应该学会如何使用它,于是自定义了一个demo:

MyTest.java

import org.junit.Test;
import org.springframework.util.StopWatch;

public class MyTest {

    @Test
    public void testWatch() throws Exception {
        StopWatch myWatch = new StopWatch("myWatch");
        doSomething(2000L, myWatch);
        doSomething(3000L, myWatch);
        doSomething(1000L, myWatch);

        System.out.println("总列表输出:");

        System.out.println(myWatch.prettyPrint());
    }

    private void doSomething(long millis, StopWatch myWatch) throws Exception {
        myWatch.start("task" + (myWatch.getTaskCount()+1));
        Thread.sleep(millis);
        myWatch.stop();
        System.out.println("StopWatch 'myWatch': LastTask time (millis) = " + myWatch.getLastTaskTimeMillis());
        System.out.println(myWatch.shortSummary());
    }
}

输出结果:


StopWatch 'myWatch': LastTask time (millis) = 2002
StopWatch 'myWatch': running time (millis) = 2002
StopWatch 'myWatch': LastTask time (millis) = 3001
StopWatch 'myWatch': running time (millis) = 5003
StopWatch 'myWatch': LastTask time (millis) = 1000
StopWatch 'myWatch': running time (millis) = 6003
总列表输出:
StopWatch 'myWatch': running time (millis) = 6003
-----------------------------------------
ms     %     Task name
-----------------------------------------
02002  033%  task1
03001  050%  task2
01000  017%  task3

扩展

你可能会问,SpringBoot的StopWatch已经这么牛批了,那在性能上还有没有优化的地方呢?

有,我想到了一个,但是觉得没必要,方法就是把stop方法中的+-换成位运算:

    long add(long num1, long num2){
        long  sum = num1 ^ num2;
        long carry = (num1 & num2) << 1;
        while(carry != 0){
            long a = sum;
            long b = carry;
            sum = a ^ b;
            carry = (a & b) << 1;
        }
        return sum;
    }
  • 为什么觉得没必要呢?因为计时器一般被用于计算(相对)长时间的任务,仅仅是换成位运算并不会带来什么特别大的提升,反而影响了代码阅读的舒适度。