java8使用parallelStream并行流造成数据丢失或下标越界异常解决方案

摘要:
此外,数组下标越界异常可能会在多次之后发生。显然,这里的代码不合逻辑。ArrayList的add方法的源代码如下:/***将指定的元素追加到此列表的末尾。**@要添加到此列表中的parameelement*@return<tt>true</tt>*/publicboolean add{secureCapacityInternal;//IncrementmodCount!!elementData[size++]=e:向elementData数组中添加元素。解决方案1:将parallelStream改为stream,或者直接使用foreach处理。在源代码中,CopyOnWriteArrayList在添加操作期间通过ReentrantLock锁定,以防止并发写入。第三,使用包装类resultList=Collections。synchronizedList;综上所述,在选择流和并行流方法时,我们可以考虑以下问题:1。我们需要并行吗?

描述

我们先看一段使用了并行流的代码

    @Test
    public void testStream() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
        List<Integer> streamList = new ArrayList<>();
        list.parallelStream().forEach(streamList::add);
        System.out.println(streamList.size());
    }

编译结果:

java8使用parallelStream并行流造成数据丢失或下标越界异常解决方案第1张

观察发现,原来集合中的数据有10000条,但是使用并行流遍历数据插入到新集合streamList中后,新的集合中只有5746条数据。并且会在多次之后可能会出现数组下标越界异常,显然这里的代码是不合逻辑的。

分析

parallelStream中使用的是ForkJobTask。Fork/Join的框架是通过把一个大任务不断fork成许多子任务,然后多线程执行这些子任务,最后再Join这些子任务得到最终结果。关于分支/合并框架的使用案例可以看我的这篇文章(用分支/合并框架执行并行求和)。从程序上看,就是先将list集合fork成多段,然后多线程添加到streamList的结合中,而streamList是ArrayList类型,它的add方法并不能保证原子性。

ArrayList的add方法源码如下:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可以看到add方法可以概括为以下两个步骤

  1. ensureCapacityInternal(),确认下当前ArrayList中的数组,是否还可以加入新的元素。如果不行,就会再申请一个:int newCapacity = oldCapacity + (oldCapacity >> 1) 大小的数组(这个容量相当于:1 + 1/2 = 1.5倍),然后将数据copy过去。
  2. elementData[size++] = e:添加元素到elementData数组中。

在并发情况下,如果同时有A、B两个线程同时执行add,在第一步ensureCapacityInternal校验数组容量时,A、B线程都发现当前容量还可以添加最有一个元素,不需扩容;因此进入第二步,此时,A线程先执行完,数组容量已满,然后B线程再对elementData赋值时,就会抛出“ArrayIndexOutOfBoundsException”。

解决方案

第一种:将parallelStream改成stream,或者直接使用foreach处理。这可以通过判断并发处理真实能带来多大的好处,做取舍。

第二种:使用resultList =new CopyOnWriteArrayList<>(); 这是个线程安全的类。从源码上看,CopyOnWriteArrayList在add操作时,通过ReentrantLock进行加锁,防止并发写。不给过CopyOnWriteArrayList,每次add操作都是把原数组中的元素拷贝一份到新数组中,然后在新数组中添加新元素,最后再把引用指向新数组。这会导致频繁的对象创建,况且数组还是需要一块连续的内存空间,如果有大量add操作,慎用。

第三种:使用包装类 resultList = Collections.synchronizedList(Arrays.asList());

总结

在从stream和parallelStream方法中进行选择时,我们可以考虑以下几个问题:

1.是否需要并行?

2.任务之间是否是独立的?是否会引起任何竞态条件?

3.结果是否取决于任务的调用顺序?

对于问题1,在回答这个问题之前,你需要弄清楚你要解决的问题是什么,数据量有多大,计算的特点是什么?并不是所有的问题都适合使用并发程序来求解,比如当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级。

对于问题2,如果任务之间是独立的,并且代码中不涉及到对同一个对象的某个状态或者某个变量的更新操作,那么就表明代码是可以被并行化的。

对于问题3,由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。

免责声明:文章转载自《java8使用parallelStream并行流造成数据丢失或下标越界异常解决方案》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇python 连接informixcocos creator 倒计时code下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

一篇文章教会你创建vue项目和使用vue.js实现数据增删改查

【一、项目背景】 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品、修改商品价格、删除商品、查询商品,我们应该关注这些数据的操作和处理。 【二、项目目标】 主要有以下5个目标: 1、如何创建vue项目。 2、数据添加方法:获取到id和name在data上面获取,组织一个对象,把对象通过数组的相关方法,添加到当前dat...

adb连接不上手机,提示“List of devices attached”及相关操作

常用指令合集: 1、adb devices  查看连接的设备 2、adb logcat -v -time > 1.log (文件名可随意更改)   启动adb 1.打开cmd首次使用adb devices,出现下面这两句,就是正在启动adb服务 C:Usersadmin>adb devices List of devices attached...

Java Stream() 两个对象数组根据ID获取交集

@Test public void testStream01(){ ArrayList<ChargeStudent> stu1 = new ArrayList<>(); ArrayList<ChargeStudent> stu2 = new ArrayList<>...

js获取节点

1. 通过顶层document节点获取:(1) document.getElementById(elementId):该方法通过节点的ID,可以准确获得需要的元素,是比较简单快捷的方法。如果页面上含有多个相同id的节点,那么只返回第一个节点。 如今,已经出现了如prototype、Mootools等多个JavaScript库,它们提供了更简便的方法:$(i...

Java代码优化总结

  代码优化是一个很重要的课题。一般来说,代码优化的目标主要有两个,一个是减小代码的体积,另一个是提高代码运行的效率。   代码优化的细节有很多,此处列举部分:   1、尽量指定类、方法的final修饰符。   带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是fina...

【JavaScript数据结构系列】02-栈Stack

【JavaScript数据结构系列】02-栈Stack码路工人 CoderMonkey转载请注明作者与出处 ## 1. 认识栈结构 栈是非常常用的一种数据结构,与数组同属线性数据结构,不同于数组的是它是一种受限的线性结构。 画一张图来说明: 如上图所示, 最新入栈的元素/最底下的元素,为栈底 最后一个/最上面的元素,为栈顶 最后一个入栈元素最先出栈(L...