文章目录
前言
在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;
}
- 为什么觉得没必要呢?因为计时器一般被用于计算(相对)长时间的任务,仅仅是换成位运算并不会带来什么特别大的提升,反而影响了代码阅读的舒适度。