使用并行流造成丢失数据问题!

一、背景
有一个活动发券的业务接收一个集合类型的数据,然后通过for去遍历处理,将符合条件的数据添加到另一个list集合中,后面这种串行处理的性能比较差,为了提高处理速度,采用JDK8提供的parallelStream并行流去遍历,性能提升非常明显。 模拟代码片段如下:
for(int k=0;k<100;k++){
    List<ActItem> result = Lists.newArrayList();
    Set<Integer> idSet = Sets.newHashSet(
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
    idSet.parallelStream().forEach(i -> {
        ActItem actItem = getById(i);
        if(actItem != null){
            // 出问题的地方
            result.add(actItem);
        }
    });
    System.out.println(result.size());
}
二、产生的问题
   该优化方案上线后,出现非常诡异的事情,偶尔出现list集合中缺数据的情况。

三、原因分析
并发下ArrayList的add方法非线程安全的。
1)ArrayList的add方法源码如下:
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
2)add方法分为以下两个步骤:
ensureCapacityInternal(),确认下当前ArrayList中的数组,是否还可以加入新的元素, 如果不行,就会再申请一个:int newCapacity = oldCapacity + (oldCapacity >> 1) 大小的数组(1 + 1/2 = 1.5倍),然后将数据copy过去;elementData = Arrays.copyOf(elementData, newCapacity)
elementData[size++] = e,添加元素到elementData数组中, 在并发情况下, 如果多个线程同时执行add方法,在第一步ensureCapacityInternal校验数组容量时,都发现当前容量还可以添加元素,就都在size++的位置添加各自的新元素,会出现同一位置的值被覆盖情况,所以可能会出现数据丢失问题;此外,当数组容量正好还差1个就满时,由于都已判定不需要扩容,有一个线程先执行完,数组容量已满,其他线程对elementData赋值,就会抛出“ArrayIndexOutOfBoundsException”异常

3)parallelStream底层是基于ForkJoinPool,,Fork/Join的框架是通过把一个大任务不断fork成许多子任务,然后多线程执行这些子任务,最后再Join这些子任务得到最终结果。问题代码中,就是先将idSet集合fork成多段,然后通过多线程将中间数据添加到result中,而result是ArrayList类型,它的add方法并不能保证原子性,所以就会出现第2)步看到的数据丢失或数组越界的问题。

四、解决方法
方法一:将parallelStream改成stream,或者直接使用foreach处理,用串行的方式,就不会存在并发问题,这样优化久失去意义了。

方法二:使用Vector集合,Vector的add方法是安全的,用了Synchronized修饰。

方法三:使用包装类 List synchronizedList = Collections.synchronizedList(new ArrayList<>), 如其名,用synchronized修饰的方法。

方法四:使用List copyOnWriteArrayList =new CopyOnWriteArrayList<>(); 这是个线程安全的类,CopyOnWriteArrayList在add操作时,通过ReentrantLock进行加锁,防止并发写。不给过CopyOnWriteArrayList,每次add操作都是把原数组中的元素拷贝一份到新数组中,然后在新数组中添加新元素,最后再把引用指向新数组。这会导致频繁的对象创建,况且数组还是需要一块连续的内存空间,如果有大量add操作,慎用。
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值