一处隐蔽的空指针异常

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

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

大致的代码是这样的:

 

 

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天然是线程安全的`给误导了。





posted @ 2017-03-08 22:54  活在夢裡  阅读(1784)  评论(1编辑  收藏  举报