parallelStream流式陷阱

1,736 阅读2分钟

这是我参与 8 月更文挑战的第 7天,活动详情查看: 8月更文挑战

java8的新特性,lambda表达式,各种一连串的操作带来代码方面的简洁性,简洁的同时带来了排查问题的复杂性,这是优缺点之间的取舍,而我们今天的猪脚是它的流式处理带来的问题;

通常情况下我们都是使用Stream来做一些处理,可是总有那么一些人我说的是一些人痴迷于多线程,所以来使用它,造成线程安全问题,甚至数据丢失,或者对这不熟悉的人(我就是压根不知道有parallelStream的人)看到别人这段代码有点懵;

  • Stream是顺序的,是以此集合作为源的顺序Stream,仅在流管道的终端操作开始后,才遍历、拆分或查询拆分器以获取估计大小,官方建议是**强烈建议拆分器报告IMMUTABLE或CONCURRENT的特征,或者是后期绑定。 否则,应该使用stream(Supplier, int, boolean)来减少对源的潜在干扰范围,**如果这里你用Stream(),它会以顺序流的方式进行数据处理,因为默认实现从集合的Spliterator创建一个顺序Stream ;
  • parallelStream是并行的,它返回一个可能的并行Stream ,以此集合作为其源。 允许此方法返回顺序流,如果这里你用parallelStream(),它会以并行流的方式进行数据处理,因为默认实现从集合的Spliterator创建一个并行Stream ;

为什么呢,因为不管Stream还是parallelStream,他们其实调用的都是一个方法,只不过参数不同,他们最终都会调用

public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

当parallel如果为true则返回的流是并行流; 如果为false则返回的流是顺序流

那并行处理问题就来了,比如我项目的一个问题,流程是多线程并发执行,创建的map加了synchronized,以为高枕无忧,在流上用parallelStream,最后流内部居然用非线程安全的集合来add值,那么问题就来了,你并行,你外面再安全,可你里面却没注意到,一个简单的非线程安全集合,会造成集合扩容不及时导致溢出,造成add同一个地方出现空值问题,抛出java.util.ConcurrentModificationException异常信息等等;

接下来我们以两个例子说明一下这两者的机制

Stream

List<Character> numbers = Arrays.asList('a', 'b', 'c', 'd', 'e', 'f', 'g');
        numbers.stream().forEach(charat -> System.out.print(charat +" ")

结果是:a b c d e f g

parallelStream

List<Character> numbers = Arrays.asList('a', 'b', 'c', 'd', 'e', 'f', 'g');
        numbers.parallelStream().forEach(charat -> System.out.println(charat +" "+Thread.currentThread().getName())
        );

结果是:e main 

g ForkJoinPool.commonPool-worker-2 

b ForkJoinPool.commonPool-worker-1

 f ForkJoinPool.commonPool-worker-2 

c ForkJoinPool.commonPool-worker-1 

a ForkJoinPool.commonPool-worker-2

 d main

这里是调用三个线程来处理数据,所以你看吧,如果这里你用非线程安全集合来处理复杂的业务数据那么结果可想而知,所以,我们应该假装不知道parallelStream,尽量用Stream来处理,又恰好Stream是顺序的,所以在加锁方面我们没必要加锁,因为Stream的顺序流就相当于锁了;而且在我们使用parallelStream,某些情况下会造成线程过度占用,影响资源分配,

这类问题非常难排查,除非你有很高的经验,

最后,七夕快乐啊