【问题标题】:List setting null values with the parallel stream使用并行流列出设置空值
【发布时间】:2018-10-17 18:24:46
【问题描述】:

我有以下代码

final String arr[] = {"A", "B", "C", "D"};
final List<String> list1 = new ArrayList<>();
final List<String> list2 = Arrays.asList(arr).parallelStream()
                          .peek(s -> list1.add(s + s))
                          .map(s -> s + s)
                          .collect(Collectors.toList());
System.out.println(list1);
System.out.println(list2);

我得到以下结果:

[null, AA, DD, null]
[AA, BB, CC, DD]

为什么某些元素在list1 中显示null? 我知道我应该使用SynchronizedListparallelStream,但null 的值让我感到惊讶。 一旦您向列表中添加了某些内容,就应该设置该插槽,并且下一次执行应该为其添加另一个值。

【问题讨论】:

  • 我无法复制。你用的是哪个java版本?
  • @Eran 这将很难重现,也不是来自单个调用......无论如何它是非常不确定的
  • @Eugene 我尝试了多次。有时我在 list1 列表中有 3 个元素,有时是 4 个,但我从来没有得到 null 元素。
  • 我运行了你的程序并没有得到任何空值,但有时会得到 3 个元素
  • @fastcodejava 谢谢我看到了答案,我还没有运行 Eugene 的代码,但是又运行了很多次你的代码并且确实看到了一次空值。没有什么特别的配置,使用的是 Java 8

标签: java parallel-processing java-stream


【解决方案1】:

ArrayList 不是一个线程安全的集合,你尝试使用parallelStream从多个线程更新它,你看到的是resizing的内部数组的结果ArrayList - 该操作也不是线程安全的,您首先增加数组并然后将元素移动到其中:可能是元素尚未移动并且您尝试访问它。

为了更明确,当调整大小时,会创建一个增加大小的 数组 - 如果在复制后充满nulls ,则此新数组发生。

真的无法预测会发生什么,只是不要依赖任何这些。

编辑

我不能将此作为评论发布,但这里有一些代码证明可以看到null

List<Integer> result = IntStream.iterate(0, i -> i + 1)
            .takeWhile(i -> {
                String arr[] = { "A", "B", "C", "D", "E", "F", "G", "H", "R", "T" };
                List<String> list = new ArrayList<>();
                Arrays.stream(arr)
                        .parallel()
                        .peek(list::add)
                        .forEach(x -> {
                        });

                if (list.contains(null)) {
                    System.out.println("It took " + i + " steps to see a null");
                    return false;
                }

                return true;
            })
            .boxed()
            .collect(Collectors.toList());

【讨论】:

  • 看到null 值应该不足为奇,因为数组在设置之前就充满了null 值。 +1
  • 请注意,在顺序执行中,根本不会有调整大小的操作,因为 OP 的四个元素和您的十个元素都在ArrayList 的默认容量之内。调整大小操作已经是竞争条件的产物。这些竞争条件的可能性随着元素的数量而增加,这就是为什么您的示例具有更高的 null 值的可能性,而 OP 的示例更可能产生异常,如果您经常运行它。
  • 我怀疑这与调整大小有关 - 正如 Holger 指出的那样,所有元素都适合默认容量。我想这更可能是使用size 计算索引、设置元素和在add 方法中递增size 之间的竞争条件。多个add 调用写入同一个索引,每次递增size,导致跳过的索引仍保留null
  • 通过稍微调整的测试代码(我使用的是 java 8,我没有 takeWhile)我发现null 最有可能被观察到的位置是索引 0 - 这让我很惊讶很少(在确实包含空值的 10000 次迭代中观察到空值:结果:0=9789;1=2528;2=995;3=519;4=423;5=366;6=316;7=256;8=154; 9=1)。在我的实验中,列表的大小从不大于 10。 (我这样做只是出于好奇——我知道推理非线程安全代码的多线程行为毫无意义)。
  • @Hulk 多个 add 调用写入同一个索引,每次都会增加大小,导致跳过的索引保持为空,这是解释它的好方法!但无论哪种方式,都很难推理 如何 这会破坏 - 事实是,它会
猜你喜欢
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 2021-04-11
  • 1970-01-01
  • 2012-09-11
  • 2016-10-09
相关资源
最近更新 更多