1. 前言
在说 parallelStream 之前, 一定要了解 Stream 以及它的基本操作
2. 什么是 ParallelStream
上文讲到的 Java8 Stream 流在执行时候是串行化的, 如果说任务执行的耗时比较长, 可以使用 Stream 的 "兄弟流" ParallelStream
防止误导, 并非耗时就一定要使用并行, 根据不同的业务场景, 合理的使用即可
parallelStream 是一种并行流, 意思为处理任务时并行处理, 这里和并发编程就有了千丝万缕的关系
前提是硬件支持, 如果单核 CPU, 只会存在并发处理, 而不会并行
这篇文章主要是说明 ParallelStream 其中一个可能为成为埋雷的点
项目中业务使用的并行流真的会都并行处理么?
3. 如何使用 ParallelStream
ParallelStream 在使用上与 Stream 无区别, 本质返回的都是一个流, 只不过底层处理时 根据条件判断是并行或者是串行
并行流并不会按照原本的顺序轨迹执行, 而是 随机执行, 当然对于这种 forEach 输出也可以做到顺序串行, 但这个不在文章中的重点
4. ForkJoinPool
相信如果在项目中实际使用过并行流的小伙伴, 一定会知道 ForkJoinPool
没错, 并行流底层就是使用的 ForkJoinPool, 一种 工作窃取算法线程池
ForkJoinPool 的优势在于, 可以充分利用多 CPU 的优势,把一个任务拆分成多个"小任务", 把多个"小任务"放到多个处理器核心上并行执行; 当多个"小任务"执行完成之后, 再将这些执行结果合并起来
5. 并行流的陷阱
5.1 线程安全问题
只要是并行处理, 如果在流程中的操作产生了竞态条件, 就会存在线程安全问题
这里举个例子进行说明具体问题
public static void main(String[] args) {
List<Integer> integerList = Lists.newArrayList();
List<String> strList = Lists.newArrayList();
int practicalSize = 1000000;
for (int i = 0; i < practicalSize; i++) {
strList.add(String.valueOf(i));
}
strList.parallelStream().forEach(each -> {
integerList.add(Integer.parseInt(each));
});
log.info(" >>> integerList 预计长度 :: {}", practicalSize);
log.info(" >>> integerList 实际长度 :: {}", integerList.size());
}
/**
* >>> integerList 预估长度 :: 1000000
* >>> integerList 实际长度 :: 211195
*/
复制代码
上面这段程序运行流程说明如下:
1、创建了两个 List, 分别是 String、Integer 类型
2、向 strList 插入 1000000 条记录
3、使用并行流将 strList 中的数据格式化为 Integer 并添加到 integerList
4、输出 integerList 预计长度以及实际长度
正常情况下, 我们是希望 integerList 最终输出 1000000
但是会因为并