我有一个静态ArrayList.Each线程运行来洗牌ArrayList,然后我得到了错误的结果。
public void collectoinTest() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 10000; j++) {
Collections.shuffle(list);
System.out.println( list);
}
}
}).start();
}
}
输出看起来像这个somtime:
[8, 9, 6, 5, 1, 7, 3, 4, 6, 0]
它有重复的元素,任何人都可以解释这个?
答案 0 :(得分:4)
shuffle内部工作的方式是交换列表元素:
T tmp = list.get(from);
list.set(from, list.get(to));
list.set(to, tmp);
如果您有多个线程,则线程的操作可能会交错,例如:
T tmp1 = list.get(from1);
T tmp2 = list.get(from2);
list.set(from2, list.get(to2));
list.set(from1, list.get(to1));
list.set(to2, tmp2);
list.set(to1, tmp1);
或
T tmp1 = list.get(from1);
T tmp2 = list.get(from2);
list.set(from1, list.get(to1));
list.set(to1, tmp1);
list.set(from2, list.get(to2));
list.set(to2, tmp2);
但是交错的顺序是不确定的:它取决于许多难以预料的事情。特别是因为有100个线程在同一个列表上工作。
某些潜在的交错可能会导致写入错误的值,因为from
的值不再是您之前写入的值。
考虑列表[0, 1]
的一个非常简单的示例,以及两个尝试交换元素的线程(所以from1 = from2 = 0; to1 = to2 = 1;
)。如果它们像这样交错(仅作为一个示例;其他交错可能具有相同的效果):
T tmp2 = list.get(0);
T tmp1 = list.get(0);
list.set(0, list.get(1));
list.set(1, tmp2);
list.set(0, list.get(1));
list.set(1, tmp1);
然后最终列表为[0, 0]
。 Ideone demo
有两种方法可以避免这种情况:
list.subList
来提取列表的一部分视图)。然后线程不会干扰,因为它们在数据的不同部分上运行。然而,洗牌将仅限于在这些子列表中进行交换;元素无法在列表中移动,就像您一次性对整个列表进行随机播放一样。答案 1 :(得分:1)
您有多个线程可以更新您的列表。由于您未使用任何同步,因此可能由于这些线程所做的更改而导致列表的状态不一致。两个线程可以使用两个不同的值更新相同的元素位置,并最终得到重复的值。
您应该像这样同步您的收藏:
synchronized (list) {
Collections.shuffle(list);
System.out.println(list);
}
只要您不从run
方法之外更新列表,上述代码就可以正常工作。如果这样做,可能需要将列表声明为字段或静态成员,然后使用适当的锁。