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可以领取面试资料