JAVA使用并行流(ParallelStream)使用中的坑

本文探讨了Java中并行流操作List时遇到的线程安全问题,如null值和集合长度异常。作者通过实例展示了为何`filter`后的元素数量不稳定,并提出了使用synchronizedList或collect方法确保线程安全的解决方案。
摘要由CSDN通过智能技术生成
public class TestParallelStream {
    public static void main(String[] args) {
        printFun();
    }

    public static void printFun() {
        List<Integer> integersList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            integersList.add(i);
        }
        //普通集合 存储
        List<Integer> parallelStorage = new ArrayList<>();
        //同步集合 存储
        List<Integer> parallelStorage2 = Collections.synchronizedList(new ArrayList<>());
        //通过并行流存入普通集合parallelStorage中
        integersList
                .parallelStream()
                .filter(i -> i % 2 == 0)
                .forEach(i -> parallelStorage.add(i));
        System.out.println("开始打印普通集合parallelStorage长度:"+parallelStorage.size());
        parallelStorage
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));
        System.out.println();
        System.out.print("------------------------------------");
        System.out.println();
        //通过并行流存入同步集合parallelStorage2中
        integersList
                .parallelStream()
                .filter(i -> i % 2 == 0)
                .forEach(i -> parallelStorage2.add(i));
        System.out.println("开始打印同步集合parallelStorage 长度:"+parallelStorage2.size());
        parallelStorage2
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));
        Collections.sort(parallelStorage2);
        System.out.println();
        System.out.println(parallelStorage2);
    }
}

获取到的结果可能是:

开始打印普通集合parallelStorage
66 62 64 16 12 14 22 24 18 32 20 82 34 36 84 86 6 8 28 30 10 78 80 4 76 26 0 2 68 70 44 56 58 60 54 48 50 46 52 40 42 38 74 72 96 98 90 92 94 88 
------------------------------------
开始打印同步集合parallelStorage
66 90 56 92 58 82 88 62 60 64 84 54 86 72 78 80 50 52 32 76 94 34 36 98 68 70 28 30 74 26 44 16 12 48 14 6 8 46 22 24 4 18 0 2 20 96 10 40 42 38 

或者


开始打印普通集合parallelStorage
66 62 64 72 74 68 70 32 34 36 28 30 26 44 48 46 40 42 38 4 0 2 null null 12 14 24 18 20 54 90 82 92 50 52 84 88 86 78 80 96 76 98 94 10 6 56 8 58 60 
------------------------------------
开始打印同步集合parallelStorage
66 62 64 32 56 90 34 16 92 36 58 72 12 88 28 30 74 14 60 68 26 70 54 22 50 44 52 24 18 48 40 20 42 46 82 38 6 8 10 96 84 86 4 94 98 0 2 78 80 76 

获取的值有null值并且长度不等于50,按照理想来说的话: filter(i -> i % 2 == 0)只会获取50个数值,长度竟然变化的,甚至可能会出现ArrayIndexOutOfBoundsException,越界了。。。。。

长度以及数据的存放最根本的方法应该就是add的方法

从代码中可以看出来这并不是线程安全的一种操作:list底层是个数组

1.读取数组的size

2.将数据e放入数组size中,elementData[size] = e;

3.将size + 1

4.保存size

这样就会产生有些数据是null值,下图描述了产生这种情况的粗略解释:

如何解决上面的问题呢???

方法1:使用一个线程安全的数组

Collections.synchronizedList(new ArrayList<>()) 

方法二:直接使用collect();这种收集起来所有元素到新集合是线程安全的

List<Integer> collect = integersList
                .parallelStream()
                .filter(i -> i % 2 == 0).collect(Collectors.toList());

原理:parallelStream底层依赖Fork/Join框架

    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

Fork/Join的思想是分治,先拆分任务,再合并结果,每个任务都用单独的线程去处理。所以虽然它同样使用ArrayList,但是我们看到他会为每个线程都创建一个ArrayList对象,最后用addAll方法把它们合并起来,每个线程操作的是自己的集合对象,自然不会有线程安全问题。

总结:这归根结底是一个线程安全问题,与parallelStream并行处理并没有半毛钱的关系,因此在使用多线程处理问题时候要充分考虑一下集合处理的安全问题,最根本的原因还是集合使用的错误

 

骚年 想知道更多的套路吗?欢迎关注:叮铛的公众号

回复  8888可以领取面试资料

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术王老五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值