前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >被 Java8 ParallelStream 的 bug 坑死了!

被 Java8 ParallelStream 的 bug 坑死了!

作者头像
Bug开发工程师
发布2019-10-11 11:25:31
7.3K2
发布2019-10-11 11:25:31
举报
文章被收录于专栏:码农沉思录码农沉思录
作者:神秘的寇先森 原文:https://cloud.tencent.com/developer/article/1139580

Java8并行流ParallelStream和Stream的区别就是支持并行执行,提高程序运行效率。但是如果使用不当可能会发生线程安全的问题。Demo如下:

代码语言:javascript
复制
public static void concurrentFun() {
        List<Integer> listOfIntegers =
                new ArrayList<>();
        for (int i = 0; i <100; i++) {
            listOfIntegers.add(i);
        }
        List<Integer> parallelStorage = new ArrayList<>() ;
        listOfIntegers
                .parallelStream()
                .filter(i->i%2==0)
                .forEach(i->parallelStorage.add(i));
        System.out.println();

        parallelStorage
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));

        System.out.println();
        System.out.println("Sleep 5 sec");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        parallelStorage
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));
    }

程序运行结果如下:

代码语言:javascript
复制
null 72 56 58 60 74 34 36 68 70 54 28 30 50 52 26 16 44 12 14 48 22 46 40 24 42 18 20 38 6 8 10 0 null 4 82 66 84 86 78 80 76 62 64 90 92 94 88 96 98 
Sleep 5 sec
null 72 56 58 60 74 34 36 68 70 54 28 30 50 52 26 16 44 12 14 48 22 46 40 24 42 18 20 38 6 8 10 0 null 4 82 66 84 86 78 80 76 62 64 90 92 94 88 96 98 

除了以上在ForEach里面添加集合元素会出现这种问题,以下这种方式也会:

代码语言:javascript
复制
              listOfIntegers
                .parallelStream()
                .map(e -> {
                    parallelStorage.add(e);
                    return e;
                })
                .forEachOrdered(e -> System.out.print(e + " "));

两个问题:1.为什么parallelStorage的大小不固定?2.为什么parallelStorage会有null元素?

最初我以为是因为主线程执行完成后并行流中的线程并未结束,sleep了主线程后发现结果并没有发生改变,其实我们可以认为ArrayList内部维护了一个数组Arr其定义一个变量 n用以表式这个数组的大小那么向这个ArrayList中存储数据的过程可以分解为这么几步:

1.读取数组的长度存入n 2.向这个数组中储入元素arr[n]=a 3.将n+1 4.保存n 而对于parrallelStorage元素数量不固定的原因就是多线程有可能同时读取到相同的下标n同时赋值,这样就会出现元素缺失的问题了 如何解决这个问题呢?我们可以将其转化为一个同步集合也就是

代码语言:javascript
复制
Collections.synchronizedList(new ArrayList<>()) 

在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序。

除了以上这种方式,还有什么方法可以防止并行流出现线程不安全操作?

那就是最后调用collect(Collectors.tolist()),这种收集起来所有元素到新集合是线程安全的。Demo如下:

代码语言:javascript
复制
public static void collectFun() {
        List<Integer> listOfIntegers =
                new ArrayList<>();

        for (int i = 0; i <100; i++) {
            listOfIntegers.add(i);
        }

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


        System.out.println();

        parallelStorage
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));

        System.out.println();
        System.out.println("Sleep 5 sec");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        parallelStorage
                .stream()
                .forEachOrdered(e -> System.out.print(e + " "));
    }

程序运行结果如下:

代码语言:javascript
复制
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 
Sleep 5 sec
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 

不光没有出现Null和数量不一致问题,还排序了!所以,在采用并行流收集元素到集合中时,最好调用collect方法,一定不要采用Foreach方法或者map方法。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 除了以上这种方式,还有什么方法可以防止并行流出现线程不安全操作?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档