• 一处隐蔽的空指针异常


    记录一个遇到的隐蔽的空指针异常。

    公司里的测试同事之前发现项目里有处偶现的空指针异常。

    大致的代码是这样的:

    private void doWriteTransDetails(List<TransDetailDTO> transDetails) {
      Map<String, List<TransDetailDTO>> transDetailsInvestorMap =
    transDetails.stream().collect(Collectors.groupingBy(item -> item.getInvestor().getName()));

    // 省略
      ......
    }

    对于固定的测试集,多次重跑偶现空指针出现在groupingBy中,乍一看推断是item.getInvestor()为null导致getName()出现空指针。但经过debug查看,实际上是偶现item为null的情况,对于固定的测试集,不应该有这样的偶然因素。

    测试猜测是否是因为多线程环境造成的线程安全问题?但这个list在最上游是在方法中定义的,封闭在栈中,不可能有多个线程操作它。

    经过排查,终于在上游代码中某处发现了元凶。

    public void processTransAppLyList(List<TransDetailDTO> transDetails, List<TransApplyDTO> transApplys) {
      // 省略
      ......
    transApplys.parallelStream().forEach(trans -> {
    TransDetailDTO transDetailDTO = new TransDetailDTO();
    // 省略
         ......
    transDetails.add(transDetailDTO);
    });
      // 下游调用省略
      ......
    }

    源头就是因为此处代码用了并行流,并行流是基于Fork/Join框架实现的。直白点说,这里`transApplys.parallelStream().forEach`是一个多线程的并发环境,对一个`transDetails`进行add操作具有不可预期的结果,可能会数组越界,也可能会元素丢失,也可能会部分index的引用为null。

    由于此处数据量不是太大,且操作复杂度不高,直接把并行流改为普通流即可修复。

    这个比较隐蔽的空指针异常的经验教训是:开发者在考虑用并行流的时候需要再仔细想一下,有必要吗?甚至可以考虑一下有必要用流吗?如果用并行流仍然是需要注意线程安全问题,不要被笼统的`lambda表达式/stream天然是线程安全的`给误导了。





  • 相关阅读:
    B
    给定二叉树先序、中序遍历序列,求后序遍历
    24点游戏dfs求解
    设计模式之单例模式
    生产者—消费者模式示例
    LeetCode(3):Longest Substring Without Repeating Characters
    LeetCode(5):Longest Palindromic Substring
    LeetCode(60):Permutation Sequence
    LeetCode(50):Pow(x,n)
    LeetCode(69):Sqrt(x)
  • 原文地址:https://www.cnblogs.com/micrari/p/6523508.html
Copyright © 2020-2023  润新知