7

我偶然发现了这个奇怪的错误。似乎Collections.sort()不会以在迭代同一列表时能够检测并发修改的方式修改排序列表。示例代码:

    List<Integer> my_list = new ArrayList<Integer>();

    my_list.add(2);
    my_list.add(1);

    for (Integer num : my_list) {

        /*
         * print list
         */
        StringBuilder sb = new StringBuilder();
        for (Integer i : my_list)
            sb.append(i).append(",");
        System.out.println("List: " + sb.toString());

        /*
         * sort list
         */
        System.out.println("CurrentElement: " + num);
        Collections.sort(my_list);
    }

输出

List: 2,1,
CurrentElement: 2
List: 1,2,
CurrentElement: 2

人们会期望 a ConcurrentModificationException,但它没有被提出,并且代码可以正常工作,尽管它不应该。

4

3 回答 3

4

当您在迭代时不从集合中添加/删除元素时,为什么会抛出ConcurrentModificationException ?

请注意,只有在将新元素添加到您的集合中或在迭代时从您的集合中删除时,才会发生ConcurrentModificationException 。即,当您的集合在结构上被修改时。

(结构修改是改变这个列表的大小,或者以其他方式扰乱它,使得正在进行的迭代可能会产生不正确的结果。)

sort 不会在结构上修改您的集合,它所做的只是修改顺序。下面的代码将抛出ConcurrentModificationException,因为它在迭代时将一个额外的元素添加到集合中。

for(Integer num : my_list) {
    my_list.add(12);
    }

如果您查看Collections类中排序方法的来源,它不会抛出ConcurrentModificationException

这个实现将指定的列表转储到一个数组中,对数组进行排序,并遍历列表,从数组中的相应位置重置每个元素。这避免了由于尝试对链表进行排序而导致的 n2 log(n) 性能。

public static <T extends Comparable<? super T>> void sort(List<T> list) {
        Object[] a = list.toArray();
        Arrays.sort(a);
        ListIterator<T> i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set((T)a[j]);
        }
    }

摘自java Generics and Collections一书:

Java 2 集合的迭代器策略是快速失败,如第 11.1 节所述:每次访问支持集合时,它们都会检查它的结构修改(通常,这意味着元素已被添加或删除)集合)。如果他们检测到结构修改,他们会立即失败,抛出 ConcurrentModificationException 而不是继续尝试迭代修改后的集合,结果不可预测。

于 2012-12-11T23:37:07.310 回答
1

说到功能,我不明白为什么它不应该 throw ConcurrentModificationException。但是根据文档,迭代器在注意到结构修改时会抛出异常,并且结构修改定义为:

结构修改是那些改变列表大小的修改,或者以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。

我认为有一个论点声称sort重新排列元素会导致迭代器产生错误的结果,但我没有检查定义的迭代器的正确结果是什么。

说到实现,很容易看出它为什么不这样做:请参阅ArrayListCollections的源代码:

  • ArrayList.modCount随着所谓的结构修改而改变
  • ListItr方法复制其值init并检查其方法是否未更改
  • Collections.sort呼叫ListItr.set哪个呼叫ArratList.set。最后一种方法不会增加modCount

所以ListItr.next()看到相同modCount并且没有抛出异常。

于 2012-12-12T00:10:16.863 回答
1

对于 Android,它取决于 API 版本。从 API 26 开始,Collections#sort(List<T>, Comparator<? super T>)实际上调用List#sort(Comparator<? super E>). 因此,如果您 sort ArrayList,您可以获得ConcurrentModificationException ,具体取决于您是否在另一个线程中修改了列表。这是引发异常的源代码java/util/ArrayList.java

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
于 2020-07-20T09:19:32.577 回答