ThreadLocal与StopWatch结合统计代码运行时间
StopWatch
是springframewrk框架当中用于计时的一个秒表工具类,是线程不安全的,注意不要在多个线程同时使用,会造成计时结果不准确,
最简答的用法如下:
StopWatch stopWatch = new StopWatch();
stopWatch.start("任务一");
TimeUnit.MILLISECONDS.sleep(500);
stopWatch.stop();
stopWatch.start("任务二");
TimeUnit.MILLISECONDS.sleep(300);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
在springBoot项目启动的时候会输出 Started in xxx seconds,也是使用的这个StopWatch小组件来计时的
在内部实现也是非常的简单
整的stopwatch的id,以及内部类TaskInfo包含的LinkedList用来记录历史任务信息,每个任务名称以及耗时时间,lastTaskInfo为结束的上一个任务信息,当调用stop之后会重新赋值,keepTaskList 为布尔值,可以理解为是否记录历史的任务信息,
如果为true,则只能获取到当前任务的耗时时间,且不会往taskList记录信息,
使用StopWatch的好处就是,可以减少自定义计时时间,减少long start = System.currentTimeMillis(); end2 - end1,这样的计算方式;
让主业务的逻辑代码更清晰,同时可以根据参数时候记录每个任务的耗时占比
需要注意的地方,StopWatch内部计时非线程安全,避免多个线程同时计时,会导致totalTimeNanos以及taskCount计算混乱;
避免过度使用,个人认为,执行逻辑只有一个任务时,可以没有必要用这个,以往的System.currentTimeMillis()可能更醒目;
任务的数量不易太多,因为内部有个list会记录历史的任务,使用不当添加过多的任务也可能使内存占用过高
但是如果多个任务执行发下使用StopWatch的时候,多个不同的处理逻辑使用stopwatch会将这个变量传过来传过去,造成混乱,增加代码的复杂度,所以结合threadLocal进行计时
/**
* 运行时间工具类
*/
public class WatchUtils {
private static ThreadLocal<CustomStopWatch> threadLocal = ThreadLocal.withInitial(() -> new CustomStopWatch());
private static class CustomStopWatch extends StopWatch {
private boolean keepTaskList = true;
@Override
public void setKeepTaskList(boolean keepTaskList) {
super.setKeepTaskList(keepTaskList);
this.keepTaskList = keepTaskList;
}
@Override
public String prettyPrint() {
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\n');
if (!this.keepTaskList) {
sb.append("No task info kept");
}
else {
sb.append("---------------------------------------------\n");
sb.append("ms % Task name\n");
sb.append("---------------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(4);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (TaskInfo task : getTaskInfo()) {
sb.append(nf.format(TimeUnit.NANOSECONDS.toMillis(task.getTimeNanos()))).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append("\n");
}
}
return sb.toString();
}
}
/**
* 开始执行时间
*/
public static void start(String taskName) {
Assert.notNull(taskName, () -> "taskName不能为空");
CustomStopWatch stopWatch = threadLocal.get();
if (stopWatch.currentTaskName() != null) {
stopWatch.stop();
}
stopWatch.start(taskName);
}
public static void stop() {
CustomStopWatch stopWatch = threadLocal.get();
if (stopWatch.currentTaskName() != null) {
stopWatch.stop();
}
}
public static String prettyPrint() {
CustomStopWatch stopWatch = threadLocal.get();
if (stopWatch.currentTaskName() != null) {
stopWatch.stop();
}
threadLocal.remove();
return stopWatch.prettyPrint();
}
public static String printCurrentTask() {
CustomStopWatch stopWatch = threadLocal.get();
if (stopWatch.getLastTaskInfo() != null) {
StopWatch.TaskInfo taskInfo = stopWatch.getLastTaskInfo();
return String.format("%s 用时 %sms", taskInfo.getTaskName(), taskInfo.getTimeMillis());
}
return "";
}
}
使用起来也是特别的方便,并且无需手动stop()
因为低版本StopWatch内部使用的是毫秒,但是高版本使用的是更精确的纳秒,所以这里集成了SwtopWatch,重写了打印方法,如果是低版本的,则直接使用StopWatch,如果项目引入了Hutool。可以直接换成Hutool的StopWatch
因为在其内部重载了prettyPrint方法,加入了时间单位