我对以下代码感到困惑
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 + " "));
}
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
以下是两个问题:
Q1:为什么parallelStorage的大小不确定?
我理解使用fork / join框架的parallelStream,所以我猜这个问题是由一些没有完成工作的线程引起的,然后我暂停主线程5秒,但似乎没有帮助,大小parallelStorage仍保持不变;
Q2:为什么parallelStorage中存在null元素?
答案 0 :(得分:5)
ArrayList
不是线程安全的。这意味着如果你有两个线程同时更新列表,那么两个线程可能会以可能导致数据丢失的方式相互干扰(或者,对于某些数据结构,可能会完全破坏结构)。
我不知道您添加到ArrayList
时所采取的步骤的确切顺序,但让我们说它是这样的。 ArrayList
应包含一个支持数组,以及一个指示当前大小
N
arr[N]
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
如果在线程调用add
之前数组大小为3,请注意两个线程将3读入它们自己的局部变量N
;然后他们将新元素放在同一个地方,然后将4存储到数组大小。因此,尽管添加了两个元素&#34;,但新数组大小将为4而不是5,并且其中一个新数据元素将丢失。
这就是你需要同步列表的原因。
(在多个线程之间完成步骤的方式是不可预测的。因此,在某些情况下,不同的执行顺序可能导致在存储元素之前两个线程增加大小,这是可信的。导致数组中的元素保持未使用状态,因此为null
。请不要将我在此处发布的步骤序列作为Java运行时所采取的实际步骤;它只是一个示例,我没有查看ArrayList
代码。)
答案 1 :(得分:4)
你已经自己写了 - 这是一个有状态的 lambda,应该避免这些。 ArrayList
确实不是线程安全的,收集到这样的List
会以未经证实的方式破坏事物。特别是当列表需要在内部加倍它的大小并复制元素时。通常无法判断会发生什么(或者如果它发生在非线程安全的收集中)。
但即使添加Collections.synchronizedList
仍然是错误的,因为它不会保留顺序(如果你关心的话)。您唯一的保证是确实会收集所有元素,但以无序方式。
Integer[] intArray = { 1, 2, 3, 4, 5, 6, 7, 8 };
List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray));
List<Integer> parallelStorage = Collections.synchronizedList(new ArrayList<>(1));
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);
你唯一确定的是parallelStorage
确实拥有listOfIntegers
中的所有元素(而不是你看到null的普通ArrayList
);但否则订单仍将被破坏。
你可以很容易地看到这样的结果:
1 2 3 4 5 6 7 8 [3, 8, 5, 2, 7, 1, 4, 6]
forEachOrdered
保留遭遇顺序(如果没有被其他中间操作破坏,例如unordered
),但此顺序仅保留forEachOrdered
,但这并不意味着在遇到订单中,元素仍然已处理。