# 文章目录
<div class="table-of-contents"><ul><li><a href="#%E5%89%8D%E8%A8%80">前言</a></li><li><a href="#springboot%E5%90%AF%E5%8A%A8%E4%BD%BF%E7%94%A8stopwatch">SpringBoot启动使用StopWatch</a></li><li><a href="#%E8%B7%9F%E8%BF%9B%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81">跟进部分源码</a><ul><li><a href="#stopwatch%E5%B1%9E%E6%80%A7">StopWatch属性</a></li><li><a href="#start%E6%96%B9%E6%B3%95">start方法</a></li><li><a href="#stop%E6%96%B9%E6%B3%95">stop方法</a></li></ul></li><li><a href="#%E5%AE%9E%E9%AA%8Cdemo%EF%BC%9A">实验Demo:</a></li><li><a href="#%E6%89%A9%E5%B1%95">扩展</a></li></ul></div>
# 前言
在Spring中,为了统计任务的执行时间,开发了一个轻量级的计时器,它就是:StopWatch。今天就站在用户的角度,学会让StopWatch为我们所用,顺便看看一部分源码。
> SpringBoot Version:2.1.7
# SpringBoot启动使用StopWatch
早在之前的博客中,就提到了springboot启动流程中用到了计时器,现在再去瞧一瞧:
`org.springframework.boot.SpringApplication#run(java.lang.String...)`
```java
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属性
```java
/** 计时器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)`
```java
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方法
```java
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`
```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());
}
}
```
输出结果:
```java
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方法中的+-换成位运算:
```java
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;
}
```
- 为什么觉得没必要呢?因为计时器一般被用于计算(相对)长时间的任务,仅仅是换成位运算并不会带来什么特别大的提升,反而影响了代码阅读的舒适度。

【Spring/SpringBoot】StopWatch计时器使用