在多个线程中混洗ArrayList

时间:2017-07-18 07:59:10

标签: java arraylist

我有一个静态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]

它有重复的元素,任何人都可以解释这个?

2 个答案:

答案 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方法之外更新列表,上述代码就可以正常工作。如果这样做,可能需要将列表声明为字段或静态成员,然后使用适当的锁。