StopWatch的使用,替换System.currentTimeMillis()

本文介绍了如何在开发中利用Spring的StopWatch和Apache Commons Lang的StopWatch类来轻松监测代码执行时间,包括它们的用法、源码解析和注意事项。通过StopWatch,开发者可以优雅地跟踪任务间的时间消耗并生成详细的日志输出。

背景:
有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,一般我们检测某段代码执行的时间,都是以如下方式来进行的:


public static void main(String[] args) {
  Long startTime = System.currentTimeMillis();
  // 你的业务代码
  Long endTime = System.currentTimeMillis();
  Long elapsedTime = (endTime - startTime) / 1000;
  System.out.println("该段总共耗时:" + elapsedTime + "s");
}

事实上该方法通过获取执行完成时间与执行开始时间的差值得到程序的执行时间,简单直接有效,但想必写多了也是比较烦人的,尤其是碰到不可描述的代码时,会更加的让人忍不住多写几个bug聊表敬意,而且如果想对执行的时间做进一步控制,则需要在程序中很多地方修改。

此时会想是否有一个工具类,提供了这些方法,刚好可以满足这种场景?

我们可以利用已有的工具类中的秒表,常见的秒表工具类

有 org.springframework.util.StopWatch、org.apache.commons.lang.time.StopWatch以及谷歌提供的guava中的秒表(这个我没怎么用过)

spring 用法

StopWatch 是位于 org.springframework.util 包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块。简单总结一句,Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。

也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。

想要使用它,首先你需要在你的 Maven 中引入 Spring 核心包,当然 Spring MVC 和 Spring Boot 都已经自动引入了该包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>

对一切事物的认知,都是从使用开始,那就先来看看它的用法,会如下所示:


public static void main(String[] args) throws InterruptedException {
    StopWatch stopWatch = new StopWatch();

    // 任务一模拟休眠3秒钟
    stopWatch.start("TaskOneName");
    Thread.sleep(1000 * 3);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskTwoName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskThreeName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 打印出耗时
    System.out.println(stopWatch.prettyPrint());
    System.out.println(stopWatch.shortSummary());
    // stop后它的值为null
    System.out.println(stopWatch.currentTaskName()); 
    
    // 最后一个任务的相关信息
    System.out.println(stopWatch.getLastTaskName());
    System.out.println(stopWatch.getLastTaskInfo());
    
    // 任务总的耗时  如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用
    System.out.println("所有任务总耗时:" + sw.getTotalTimeMillis());
    System.out.println("任务总数:" + sw.getTaskCount());
    System.out.println("所有任务详情:" + sw.getTaskInfo());
}

如图所示,StopWatch 不仅正确记录了上个任务的执行时间,并且在最后还可以给出精确的任务执行时间(纳秒级别)和耗时占比,这或许就会比我们自己输出要优雅那么一些。
2.2 源码
老规矩,由浅入深。看完用法,我们来看看源码。先看下组成 StopWatch 的属性


public class StopWatch {
    /**
  * 本实例的唯一 Id,用于在日志或控制台输出时区分的。
  */
    private final String id;
    /**
  * 是否保持一个 taskList 链表
  * 每次停止计时时,会将当前任务放入这个链表,用以记录任务链路和计时分析
  */
 private boolean keepTaskList = true;
   /**
  * 任务链表
  * 用来存储每个task的信息, taskInfo由taskName 和 totoalTime组成
  */
    private final List<StopWatch.TaskInfo> taskList;
    /**
  * 当前任务的开始时间
  */
    private long startTimeMillis;
    /**
  * 
  */
    private boolean running;
    /**
  * 当前任务名称
  */
    private String currentTaskName;
    /**
  * 最后一个任务的信息
  */
    private StopWatch.TaskInfo lastTaskInfo;
    /**
  * 任务总数
  */
    private int taskCount;
    /**
  * 程序执行时间
  */
    private long totalTimeMillis;
    ...
}

接下来,我们看一下StopWatch类的构造器和一些关键方法
在这里插入图片描述
2.3 注意事项

  1. StopWatch对象不是设计为线程安全的,并且不使用同步。
  2. 一个StopWatch实例一次只能开启一个task,不能同时start多个task
  3. 在该task还没stop之前不能start一个新的task,必须在该task stop之后才能开启新的task
  4. 若要一次开启多个,需要new不同的StopWatch实例

apache 用法

StopWath是 apache commons lang3 包下的一个任务执行时间监视器,与我们平时常用的秒表的行为比较类似,我们先看一下其中的一些重要方法:
在这里插入图片描述

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.6</version>
</dependency>

Apache提供的这个任务执行监视器功能丰富强大,灵活性强,如下经典实用案例:


public static void main(String[] args) throws InterruptedException {
    //创建后立即start,常用
    StopWatch watch = StopWatch.createStarted();

    // StopWatch watch = new StopWatch();
    // watch.start();

    Thread.sleep(1000);
    System.out.println(watch.getTime());
    System.out.println("统计从开始到现在运行时间:" + watch.getTime() + "ms");

    Thread.sleep(1000);
    watch.split();
    System.out.println("从start到此刻为止的时间:" + watch.getTime());
    System.out.println("从开始到第一个切入点运行时间:" + watch.getSplitTime());
    Thread.sleep(1000);
    watch.split();
    System.out.println("从开始到第二个切入点运行时间:" + watch.getSplitTime());

    // 复位后, 重新计时
    watch.reset();
    watch.start();
    Thread.sleep(1000);
    System.out.println("重新开始后到当前运行时间是:" + watch.getTime());

    // 暂停 与 恢复
    watch.suspend();
    System.out.println("暂停2秒钟");
    Thread.sleep(2000);

    // 上面suspend,这里要想重新统计,需要恢复一下
    watch.resume();
    System.out.println("恢复后执行的时间是:" + watch.getTime());

    Thread.sleep(1000);
    watch.stop();

    System.out.println("花费的时间》》" + watch.getTime() + "ms");
    // 直接转成s
    System.out.println("花费的时间》》" + watch.getTime(TimeUnit.SECONDS) + "s");
}

结束

@Override @Transactional(rollbackFor = Exception.class) public void saveRepairMaterial(SupplementaryCallMaterialParam param,String logoDisQus) { //需求时间 Date deliveryTime = param.getDeliveryTime(); List<SupplementaryCallMaterialForm> form = param.getForm(); String factoryCode = SecurityUtils.getLoginFactoryCode(); //根据备料单号分组 Map<String, List<SupplementaryCallMaterialForm>> SupplementaryCallMaterialMap = form.stream().collect(Collectors.groupingBy(SupplementaryCallMaterialForm::getBillNo)); Iterator<Map.Entry<String, List<SupplementaryCallMaterialForm>>> iterator = SupplementaryCallMaterialMap.entrySet().iterator(); while (iterator.hasNext()){ long startTime = System.currentTimeMillis(); Map.Entry<String, List<SupplementaryCallMaterialForm>> next = iterator.next(); //备料单号 String billNo = next.getKey(); if (ObjectUtil.isEmpty(billNo)){ throw new MesBusinessException(billNo+"备料单不存在"); } TShopScheduleMaterialCheckPo tShopScheduleMaterialCheckPo = shopScheduleMaterialCheckMapper.selectOne(Wrappers.<TShopScheduleMaterialCheckPo>lambdaQuery() .eq(TShopScheduleMaterialCheckPo::getBillNo, billNo) .eq(TShopScheduleMaterialCheckPo::getDataStatus, DataStatusEnum.NORMAL.getCode()) .eq(TShopScheduleMaterialCheckPo::getScheduleId, param.getForm().get(0).getScheduleId()) .orderByDesc(TShopScheduleMaterialCheckPo::getAddTime) ); //备料单id Long scheduleMaterialCheckId = tShopScheduleMaterialCheckPo.getId(); //叫料详情 List<SupplementaryCallMaterialForm> value = next.getValue(); //生成叫料单号 String resNo =null; //生成备料单号 try { resNo = numberUtil.getIdNumberApi(RuleEnum.DocumentNameEnum.DOCUMENT_MATERIAL_DEMAND.getCode(),value,SecurityUtils.getLoginFactoryCode()); //加入年份的流水 int year = Year.now().getValue(); String yearStr = String.valueOf(year).substring(2,4); String start = resNo.substring(0,3); String end = resNo.substring(3); resNo = start+yearStr+end; }catch (Exception e){ resNo = com.springboot.cloud.mes.common.core.util.DateUtils.getDate("yyyyMMddHHmmss") + new Random().nextInt(20); } SupplementaryCallMaterialForm callMaterialForm = value.get(0); Long scheduleId = callMaterialForm.getScheduleId(); String orderNumber = callMaterialForm.getShopOrderNumber(); String operation = callMaterialForm.getOperation(); String postion = callMaterialForm.getPostion(); //MES叫料详情 ShopScheduleCallMaterialPo shopScheduleCallMaterialPo = new ShopScheduleCallMaterialPo(); List<ShopScheduleCallMaterialBillsDetailsPo> shopScheduleCallMaterialBillsDetailsPoList = new ArrayList<>(); shopScheduleCallMaterialPo.setScheduleId(scheduleId); shopScheduleCallMaterialPo.setCallMaterialBills(resNo); shopScheduleCallMaterialPo.setDeliveryTime(DateUtil.format(deliveryTime,DateStyle.YYYY_MM_DD_HH_MM_SS.getValue())); shopScheduleCallMaterialPo.setCallMaterialMethod(0); shopScheduleCallMaterialPo.setBillNo(billNo); shopScheduleCallMaterialPo.setCallStatus(new Short("0")); WipIssuePo wipIssuePo = new WipIssuePo(); List<WipIssueDetailPo> wipIssueDetailPos = new ArrayList<>(); //备料单号 wms命名错误 wipIssuePo.setResNo(billNo); wipIssuePo.setBillNo(resNo); wipIssuePo.setOrg(factoryCode); wipIssuePo.setWo(orderNumber); wipIssuePo.setCreatBy(SecurityUtils.getSysUser().getNickName()); wipIssuePo.setCreatDate(DateUtil.format(new Date(),DateStyle.YYYY_MM_DD_HH_MM_SS_EN.getValue())); wipIssuePo.setSeqNo(operation); wipIssuePo.setPostion(postion); wipIssuePo.setRequestDate(DateUtil.format(deliveryTime,DateStyle.YYYY_MM_DD_HH_MM_SS.getValue())); short resLineNo = 0; List<ShopScheduleMaterialCheckDetailsPo> pos = new ArrayList<>(); if (ObjectUtil.isNotEmpty(value)){ for (SupplementaryCallMaterialForm supplementaryCallMaterialForm : value) { String assemblyMaterialCode = supplementaryCallMaterialForm.getMaterialCode(); String assemblyMaterialName = supplementaryCallMaterialForm.getMaterialName(); Integer lineNo = supplementaryCallMaterialForm.getLineNo(); BigDecimal requireNumber = supplementaryCallMaterialForm.getRequireNumber(); String unit = supplementaryCallMaterialForm.getUnit(); BigDecimal unitUse = supplementaryCallMaterialForm.getUnitUse(); WipIssueDetailPo wipIssueDetailPo = new WipIssueDetailPo(); wipIssueDetailPo.setItemCode(assemblyMaterialCode); wipIssueDetailPo.setQty(requireNumber); wipIssueDetailPo.setUnit(unit); wipIssueDetailPo.setLineNo(String.valueOf(resLineNo)); wipIssueDetailPo.setResLIneNo(new Short(String.valueOf(lineNo))); wipIssueDetailPos.add(wipIssueDetailPo); ShopScheduleCallMaterialBillsDetailsPo shopScheduleCallMaterialBillsDetailsPo = new ShopScheduleCallMaterialBillsDetailsPo(); shopScheduleCallMaterialBillsDetailsPo.setCallMaterialBills(resNo); shopScheduleCallMaterialBillsDetailsPo.setMaterialCode(assemblyMaterialCode); shopScheduleCallMaterialBillsDetailsPo.setMaterialName(assemblyMaterialName); shopScheduleCallMaterialBillsDetailsPo.setCallNum(requireNumber); shopScheduleCallMaterialBillsDetailsPo.setUseNum(unitUse); shopScheduleCallMaterialBillsDetailsPo.setOperation(supplementaryCallMaterialForm.getOperation()); shopScheduleCallMaterialBillsDetailsPo.setLineNo(Short.valueOf(String.valueOf(lineNo))); //叫料类型 1.普通叫料 2.生产超领 3.更换物料 shopScheduleCallMaterialBillsDetailsPo.setCallType(logoDisQus); shopScheduleCallMaterialBillsDetailsPoList.add(shopScheduleCallMaterialBillsDetailsPo); //预计到货数量 BigDecimal estimateNumber = supplementaryCallMaterialForm.getEstimateNumber(); //预计到货时间 Date estimateTime = supplementaryCallMaterialForm.getEstimateTime(); //修改已叫料数 List<ShopScheduleMaterialCheckDetailsPo> tShopScheduleMaterialCheckDetailsPos = scheduleMaterialCheckDetailsMapper.selectList(Wrappers.<ShopScheduleMaterialCheckDetailsPo>lambdaQuery() .eq(ShopScheduleMaterialCheckDetailsPo::getScheduleMaterialCheckId, scheduleMaterialCheckId) .eq(ShopScheduleMaterialCheckDetailsPo::getMaterialCode, assemblyMaterialCode) .eq(ShopScheduleMaterialCheckDetailsPo::getLineNo, lineNo) ); for (ShopScheduleMaterialCheckDetailsPo tShopScheduleMaterialCheckDetailsPo : tShopScheduleMaterialCheckDetailsPos) { BigDecimal alreadyCallNum = tShopScheduleMaterialCheckDetailsPo.getAlreadyCallNum(); alreadyCallNum = ObjectUtil.isNotEmpty(alreadyCallNum) ? alreadyCallNum:BigDecimal.ZERO; tShopScheduleMaterialCheckDetailsPo.setEstimateTime(estimateTime); tShopScheduleMaterialCheckDetailsPo.setEstimateNumber(estimateNumber); tShopScheduleMaterialCheckDetailsPo.setAlreadyCallNum(alreadyCallNum.add(requireNumber)); if("1".equals(logoDisQus) && alreadyCallNum.add(requireNumber).compareTo(tShopScheduleMaterialCheckDetailsPo.getRequireNumber()) > 0) { throw new MesBusinessException(tShopScheduleMaterialCheckDetailsPo.getMaterialCode() + ":叫料数量超出需求数量!"); } pos.add(tShopScheduleMaterialCheckDetailsPo); } } } wipIssuePo.setDetail(wipIssueDetailPos); //保存叫料主信息 int i = shopScheduleCallMaterialMapper.insert(shopScheduleCallMaterialPo); if (i > 0) { Long id = shopScheduleCallMaterialPo.getId(); for (ShopScheduleCallMaterialBillsDetailsPo shopScheduleCallMaterialBillsDetailsPo : shopScheduleCallMaterialBillsDetailsPoList) { shopScheduleCallMaterialBillsDetailsPo.setShopScheduleCallMaterial(id); } shopCallMaterialBillsDetailsService.saveBatch(shopScheduleCallMaterialBillsDetailsPoList); } if (ObjectUtil.isNotEmpty(wipIssueDetailPos)) { LambdaQueryWrapper<ErpShopOrderEntity> wrapper = Wrappers.<ErpShopOrderEntity>lambdaQuery() .eq(ErpShopOrderEntity::getOrderNumber, wipIssuePo.getWo()) .eq(ErpShopOrderEntity::getFactoryCode,SecurityUtils.getLoginFactoryCode()); ErpShopOrderEntity erpShopOrder = erpShopOrderService.getOne(wrapper); List<Map<String,Object>> wmsData = new ArrayList<>(); Map<String,Object> data = new HashMap<>(); data.put("DEMANDER",SecurityUtils.getUsername()); // 叫料人 data.put("WERKS", SecurityUtils.getLoginFactoryCode()); data.put("bstkd", erpShopOrder.getBstkd()); data.put("AUFNR", wipIssuePo.getWo()); data.put("KDAUF",wipIssuePo.getBillNo()); //叫料单号 data.put("AUART", "1");// 类型 叫料类型 备料类型 data.put("LOGO_DISQUS", logoDisQus);// 触发标识 '1':工单领料 '2':超领 data.put("ENTER_DATE",param.getDeliveryTime()); data.put("ENTERED_BY",SecurityUtils.getUsername()); List<Map<String,Object>> list = new ArrayList<>(); String orderMaterial = ""; for (SupplementaryCallMaterialForm materialForm : param.getForm()) { orderMaterial = materialForm.getOrderMaterial(); Map<String,Object> detailData = new HashMap<>(); detailData.put("WERKS", SecurityUtils.getLoginFactoryCode()); //工厂 detailData.put("AUFNR", wipIssuePo.getWo());// 工单 detailData.put("POSNR", materialForm.getLineNo()); //项目行号 detailData.put("SCHEDULENO", materialForm.getScheduleNumber()); //排成号 detailData.put("LINECODE", materialForm.getLineCode()); //产线 detailData.put("OPERATION",materialForm.getOperation()); //工序 detailData.put("OPERATIONNAME",materialForm.getOperationName()); //工序 detailData.put("MAKTX",materialForm.getMaterialName());//物料名称 detailData.put("MATNR", materialForm.getMaterialCode());//物料编码 if (ObjectUtil.isNotNull(materialForm.getMaterialVersion())) { detailData.put("REVLV", materialForm.getMaterialVersion());//物料版本 } else { detailData.put("REVLV", "00");//物料版本 } detailData.put("MEINS",materialForm.getUnit());//基本计量单位 detailData.put("PSMNG",materialForm.getScheduleNum());// 订单数量 工单的计划数量 detailData.put("BDMNG",materialForm.getRequireNumber());//需求量 detailData.put("RSNUM",materialForm.getReservationNumber());//预留单 detailData.put("RSPOS",materialForm.getReservedRows());//预留行 list.add(detailData); } data.put("scheduleNum",param.getForm().get(0).getScheduleNum());//排程数量 data.put("MATNR", orderMaterial);// 物料编码 data.put("prodordItemList",list); wmsData.add(data); System.err.println("=================================================================================="); System.err.println(startTime); System.err.println(System.currentTimeMillis()); System.err.println("=================================================================================="); //wms叫料成功 JSONObject jsonObject = comanWmsAdapterService.callMaterailAndCreate(wmsData); if (!jsonObject.get("code").equals(0)) { if (jsonObject.get("code").equals(555)) { Result.fail(""); throw new MesBusinessException(MesErrorType.MATERIAL_STOCK_NOT_ENOUGH,"" + jsonObject.get("data")); } throw new MesBusinessException("wms叫料失败!" + jsonObject.get("msg")); } shopScheduleProvider.updateByIds(pos); } } }怎么优化代码
10-01
在 JDK7 中,`StopWatch`(如 Apache Commons 的 `StopWatch`)和 `System.currentTimeMillis()` 的执行效率本质上都是调用系统时间接口,因此它们在**性能差异上并不显著**。但我们可以从**实现原理、精度、易用性**等方面进行对比: --- ### ✅ 执行效率对比 | 特性 | `System.currentTimeMillis()` | `StopWatch`(Apache) | |------|-------------------------------|------------------------| | 底层调用 | `currentTimeMillis()` | 封装了 `System.currentTimeMillis()` 或 `System.nanoTime()` | | 精度 | 毫秒级 | 取决于底层实现(可选纳秒) | | 调用开销 | 极低,直接调用 JVM 接口 | 略高,有封装调用 | | 使用复杂度 | 需手动计算时间差 | 提供 start/stop/getTime 方法,使用更方便 | | 是否需要第三方库 | 否 | 是(Apache Commons) | | 性能效率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | --- ### 🔍 总结结论: - **执行效率上**:`System.currentTimeMillis()` 更高一点,因为它直接调用 JVM 接口,没有封装逻辑。 - **开发效率上**:`StopWatch` 更优,尤其在需要多次计时、暂停、复用等场景。 - **适用场景**: - 如果只是简单的一次性计时:优先使用 `System.currentTimeMillis()`。 - 如果是复杂业务逻辑、需要多次统计或更清晰的代码结构:推荐使用 `StopWatch`。 --- ### ✅ 示例代码对比: #### 使用 `System.currentTimeMillis()` ```java long start = System.currentTimeMillis(); // 执行业务逻辑 long cost = System.currentTimeMillis() - start; System.out.println("耗时:" + cost + " ms"); ``` #### 使用 `StopWatch` ```java StopWatch sw = new StopWatch(); sw.start(); // 执行业务逻辑 sw.stop(); System.out.println("耗时:" + sw.getTime() + " ms"); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值