为什么使用parallelStream访问和修改Collection得到不同的结果?

J J*_*ohn 2 java parallel-processing multithreading java-8

我对以下代码感到困惑

  public static void main(String[] args) throws InterruptedException
  {
    Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8};
    List<Integer> listOfIntegers =
            new ArrayList<>(Arrays.asList(intArray));
    List<Integer> parallelStorage = new ArrayList<>();//Collections.synchronizedList(new ArrayList<>());
    listOfIntegers
            .parallelStream()
            // Don't do this! It uses a stateful lambda expression.
            .map(e -> {
                parallelStorage.add(e);
                return e;
            })
            .forEachOrdered(e -> System.out.print(e + " "));
    System.out.println();
    parallelStorage
            .stream()
            .forEachOrdered(e -> System.out.print(e + " "));
    System.out.println();
    System.out.println("Sleep 5 sec");
    TimeUnit.SECONDS.sleep(5);
    parallelStorage
            .stream()
            .forEachOrdered(e -> System.out.print(e + " "));
}
Run Code Online (Sandbox Code Playgroud)

EveryTime执行它我得到了不同的结果,这让我很困惑,这里有一些结果:

Result 1:
1 2 3 4 5 6 7 8 
null 3 8 7 1 4 5 6 
Sleep 5 sec
null 3 8 7 1 4 5 6

Result 2?
1 2 3 4 5 6 7 8 
6 2 4 1 5 7 8 
Sleep 5 sec
6 2 4 1 5 7 8
Run Code Online (Sandbox Code Playgroud)

这有两个问题:

  • Q1:为什么parallelStorage的大小不确定?

    我理解parallelStream使用fork/join框架,所以我猜这个问题是由一些线程没有完成他们的工作引起的,然后我暂停了主线程5秒,但似乎没有帮助,parallelStorage的大小仍保持不变;

  • Q2:为什么parallelStorage中存在null元素?

ajb*_*ajb 5

ArrayList不是线程安全的.这意味着如果你有两个线程同时更新列表,那么两个线程可能会以可能导致数据丢失的方式相互干扰(或者,对于某些数据结构,可能会完全破坏结构).

我不知道添加到a时所采取的步骤的确切顺序ArrayList,但让我们说它是这样的.一个ArrayList应包含背衬阵列,并且一个实例变量,指示当前大小是什么

  • 将数组大小读入局部变量 N
  • 把新元素放进去 arr[N]
  • 添加1到 N
  • N以数组大小存储

现在假设你有两个线程这样做.由于没有同步,如果线程同时调用,则线程可以按此顺序执行以下步骤add:

Read the array size into N
                                    Read the array size into N
Put the new element in arr[N]
                                    Put the new element in arr[N]
Add 1 to N 
                                    Add 1 to N
Store N into the array size
                                    Store N into the array size
Run Code Online (Sandbox Code Playgroud)

如果在线程调用之前数组大小为3 add,请注意两个线程将3读入它们自己的局部变量N; 然后他们都将新元素放在同一个地方,然后将4存储到数组大小.因此,即使"添加"了两个元素,新的数组大小也将是4而不是5,并且其中一个新数据元素将丢失.

这就是你需要同步列表的原因.

(在多个线程之间完成步骤的方式是不可预测的.因此,在某些情况下,不同的执行顺序可能导致在存储元素之前两个线程增加大小,导致数组中的元素为因此,null请不要使用我在此处发布的步骤序列作为Java运行时所采取的实际步骤;这只是一个示例,我没有查看ArrayList代码.)

  • 注意使用`Collections.synchronizedList`不会保留顺序.唯一的保证是将收集*all*元素(例如,没有空值); 但是'parallelStorage`中的顺序仍然被打破 (2认同)
  • Java 8的`ArrayList`将后备数组的创建推迟到添加第一个元素时的点.因此,当两个或多个线程尝试添加第一个元素时,可能会创建多个数组,这可能会导致开头的"null"元素,即使没有可见性或重新排序问题.当程序运行时间超过几纳秒时,其他问题可能会出现(或变得更糟)...... (2认同)