1. 场景
今天生产反馈有异常, 看看日志是CollectionUtils.sort
空指针异常, 这一眼看就是list中的排序有空元素, 首先想到的是sql查出来的字段是否有null, 结果发现并没有, 再一看处理list, 就知道问题在哪了.
2. 代码
deptList.stream().limit(totalDept - 1)
.parallel()
.forEach(
d -> list.add(new ClassVo(d.getDeptId(), d.getOrgName(), amtType)
);
...
Collections.sort(list, Comparator.comparing(ClassVo::getDeptId));
3. 原因
其实原因大概大家都知道, 并发修改导致的数据丢失呗,
确实, 这个其实仔细一看就能发现有问题, 这个当时写的时候, 大概是因为后面有一个排序, 所以在stream流处理的时候是不需要关心list是否有序的, 所以就想用parallel处理快些, 但是具体问题出在哪, 直接看AbstractList
对add的实现:
public boolean add(E e) {
add(size(), e);
return true;
}
问题就在size
方法不是线程安全的, 看看ArrayList的size实现:
private int size;
public int size() {
return size;
}
假设A线程add的时候, size是3, B线程add的时候,size也是3,则两个线程都会往3的位置放入元素,就会覆盖数据, 导致最后数据丢失
4. 总结
- 慎用parallel和parallelStream
- 如果非要用, 确保里面不会对同一个元素修改
- 如果非要修改, 确保元素是有同步修饰的, 或者Atomic类的
- 对于数据量不大的, parallel可能更耗时间, 因为线程的切换,以及parallel是先切分list再合并, 切分合并也需要时间
- parallel后会导致list无序