众所周知,java8提供了stream API, 使开发者可以方便的对数据进行处理,这其中有steam和parallelStream。需要注意的是,parallelStream使用不当会引发一些奇怪的问题。
问题的发现
问题排查
经过排查日志,我们发现后端代码产生了空指针异常,我们排查了代码,其中有这么一块
List<com.amazonaws.services.ec2.model.Image> images = new ArrayList<>();
DescribeImagesResult imagesResponse = contextThreadLocal.get().getEC2Client()
.describeImages(imagesRequest);
imagesResponse.getImages().parallelStream().forEach(image -> {
if (Constants.OS_TYPE.WINDOWS.equalsIgnoreCase(image.getPlatform())) {
boolean isValid = StrUtil.containsIgnoreCase(image.getName(), "Base") &&
StrUtil.containsIgnoreCase(image.getName(), "English") ||
StrUtil.containsIgnoreCase(image.getName(), "Chinese");
if (isValid) {
images.add(image);
}
} else {
images.add(image);
}
});
问题的原因
Java8的paralleStream用fork/join框架提供了并发执行能力。直白点说,这里list().parallelStream().forEach
是一个多线程的并发环境,对一个images
进行add操作具有不可预期的结果,可能会数组越界,也可能会元素丢失,也可能会部分index的引用为null。
简单的验证
for (int i = 0; i < 100; i++) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list2 = new ArrayList<>();
list.parallelStream().forEach(list2::add);
// 由于多线程的不确定性,排序确保我们可以清晰的看到运行结果的差异性
list2.sort(Comparator.comparingInt(o -> o));
System.out.println(list2);
}
可以看出来,我在外部加了一个循环,循环100次,内部循环体的内容就是将list的数据赋值到list2,我们期望的结果当然是不管运行多少次,list都被正常赋值到list2中,下面我们可以来看看运行结果
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Exception in thread "main" java.lang.NullPointerException
at cn.com.concurrency.example.test.main.ParallelStream.lambda$main$0(ParallelStream.java:23)
at cn.com.concurrency.example.test.main.ParallelStream$$Lambda$2/806353501.applyAsInt(Unknown Source)
at java.util.Comparator.lambda$comparingInt$7b0bb60$1(Comparator.java:490)
at java.util.Comparator$$Lambda$3/935044096.compare(Unknown Source)
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
at java.util.TimSort.sort(TimSort.java:216)
at java.util.Arrays.sort(Arrays.java:1512)
at java.util.ArrayList.sort(ArrayList.java:1454)
at cn.com.concurrency.example.test.main.ParallelStream.main(ParallelStream.java:23)
从这个运行结果我们可以看出来
- 在第三行和第十三行发生了数据的缺失,list2并没有拿到正确的结果
- 甚至最后程序没有正常退出,而是发生了空指针异常
结论:这就是我们之前是所分析的,由于线程不安全带来的运行结果的不确定性,产生了不可预期的结果
问题的解决
- 考虑是否真的需要并发,如果不需要,改为单线程的stream即可处理
- 将容器images改为线程安全的容器,如Collections.synchronizedList(_new ArrayList<>())_;