Bootstrap

Stream并发流parallel


并行的 Stream 流,即多线程执行的流。

1. 并行流的获取

  • .parallelStream()方法,直接获取并行Stream流
  • .parallel()方法,将串行流转成并行流
public class ParallelStream {
    public static void main(String[] args) {
        //方式一:通过.parallelStream()方法,直接获取并行Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.parallelStream();
        //方式二:通过.parallel()方法,将串行流转成并行流
        Stream<String> parallelStream = list.stream().parallel();
 
        //并行Stream流测试
        Stream<Integer> stream = Stream.of(4, 5, 6, 3, 21, 56).parallel();
        stream.filter(num->{
            System.out.println(Thread.currentThread().getName() + "--" + num);
            return num>3;
        }).count();
    }
}
输出(发现并行 Stream 流是在 多个线程上执行的):
ForkJoinPool.commonPool-worker-5--6
ForkJoinPool.commonPool-worker-3--4
main--3
ForkJoinPool.commonPool-worker-4--21
ForkJoinPool.commonPool-worker-1--5
ForkJoinPool.commonPool-worker-2--56
数组
String[] array = {"a", "b", "c", "d", "e"};
// 并行流
Stream<String> parallelStreamFromArray = Arrays.stream(array).parallel();

集合
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
// 并行流
Stream<Integer> parallelStreamFromSet = set.parallelStream();

2. 并行流存在线程安全问题

public class ParallelStreamTest05 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
 
        List<Integer> listNew = new ArrayList<>();
        //使用并行流向集合中添加数据
        list.parallelStream()
                .forEach(s -> listNew.add(s));
        System.out.println(listNew.size());
    }
}
存在线程安全问题!!!

解决并行流线程安全问题:

2.1 加同步锁synchronized

public class ParallelStreamTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
 
        List<Integer> listNew = new ArrayList<>();
        Object object = new Object();
        //使用并行流向集合中添加数据.
        //解决数据安全问题:1.加同步锁
        list.parallelStream()
                .forEach(s -> {
                    synchronized (object){
                        listNew.add(s);
                    }
                });
        System.out.println(listNew.size());
 
        //另一种写法
        List<Integer> list2 = new ArrayList<>();
        Object object2 = new Object();
        IntStream.range(0,1000)
                .parallel()
                .forEach(i -> {
                    synchronized (object2){
                        list2.add(i);
                    }
                });
        System.out.println(list2.size());
    }
}

2.2 使用线程安全的容器synchronizedList

public class ParallelStreamTest06 {
    public static void main(String[] args) {
        //使用线程安全的容器
        List<Integer> list3 = new ArrayList<>();
        List<Integer> list3New = Collections.synchronizedList(list3);
        Object obj = new Object();
        IntStream.range(0,1000)
                .parallel().forEach(s -> list3New.add(s));
        System.out.println(list3New.size());
    }
}

2.3 使用Stream流中的toArray或collect操作

public class ParallelStreamTest06 {
    public static void main(String[] args) {
        //通过Stream中的toArray或collect操作
        List<Integer> list4 = new ArrayList<>();
        List<Integer> collect = IntStream.range(0, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(collect.size());
 
    }
}

3. 并行流的底层原理

之所以执行效率高,因为它底层使用的是 Fork/Join 框架。Fork/Join 框架 是在 JDK 7 中引入的,Fork/Join 框架可以将一个大任务拆分为很多小任务来异步执行。

4. 并行流使用注意事项

  • 线程安全性:确保操作是线程安全的。在并行流中,多个线程会同时操作数据,因此需要确保操作的数据结构是线程安全的,或者采取适当的同步措施,避免出现并发修改异常或数据不一致的情况。
  • 性能评估:并行流并不一定在所有情况下都能提升性能,有时甚至可能降低性能。在使用并行流之前,应该进行性能评估和测试,确保并行流确实能够带来性能的提升。
  • 任务分解:并行流会将数据分成多个子任务,并行处理这些子任务。因此,在使用并行流时,应该注意任务的分解是否合理,避免出现任务分配不均衡的情况,导致部分线程长时间等待。
  • 副作用:并行流中的操作可能会产生副作用,例如修改共享状态、I/O操作等。在使用并行流时,应该避免或者谨慎处理可能引起副作用的操作,以确保程序的正确性和可靠性。
  • 并行度调整:Java并行流的并行度默认为CPU核心数,但可以通过调整系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 来修改默认的并行度。根据实际情况,可以根据计算密集型任务或I/O密集型任务来调整并行度,以获得更好的性能表现。
  • 数据量大小:对于小规模的数据集,使用并行流可能会带来额外的开销,因为任务分解和线程管理的开销可能会抵消并行化带来的性能提升。因此,在处理小规模数据集时,可以考虑使用串行流。
  • 避免共享可变状态:避免在并行流中共享可变状态,尽量使用不可变对象或线程安全的数据结构来处理数据,以避免线程安全问题。

5. 总结

  • parallel 并行 Stream 流是 线程不安全 的;
  • parallel 并行 Stream 流使用的场景是 CPU 密集型 的,只是做到别浪费 CPU,假如本身电脑的 CPU 的负载很大,那还到处用并行流,那并不能起到作用;
  • I/O 密集型、磁盘I/O、网络I/O 都属于 I/O 操作,这部分操作时较少消耗 CPU 资源,一般并行流中 不适用于 I/O密集型 的操作,就比如使用并行流进行大批量的消息推送,涉及到了大量 I/O,使用并行流反而慢了很多;
  • 在使用并行流的时候,是 无法保证元素的顺序 的,也就是即使你使用了同步集合也只能保证元素都正确,但无法保证其中的顺序。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;