【问题标题】:Java Collections.sort() missing ConcurrentModificationExceptionJava Collections.sort() 缺少 ConcurrentModificationException
【发布时间】:2012-12-11 23:34:20
【问题描述】:

我偶然发现了这个奇怪的错误。似乎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

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

【问题讨论】:

  • 你没有两个线程;没有并发(这真的该异常是要保护的)。即便如此...... 请注意,迭代器的快速失败行为不能得到保证,因为一般来说,在存在不同步的并发修改的情况下不可能做出任何硬保证。快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。
  • 如果您将元素添加到列表中(在同一个线程中),您确实会得到该异常。对我来说,为什么将元素添加到列表中是非法操作,但更改列表不是...
  • 因为它没有在结构上修改List,所以组成列表的节点都还在,甚至没有改变顺序。您的迭代器不受节点中包含的值更改的影响。简而言之,它真的不是为了保护你自己。

标签: java sorting collections


【解决方案1】:

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

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

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

sort 不会在结构上修改您的 Collection,它所做的只是修改顺序。 下面的代码会抛出 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 而不是 继续尝试迭代修改后的集合 不可预知的结果。

【讨论】:

  • +1 - 您也可以指向Collections.sort() 的文档,该状态为数组中对应的位置
  • 我知道。我也检查了那个代码。我仍然发现应该在sort() 中设置这样一个标志。
  • 公平点。所以它是著名的“它是一个功能,而不是一个错误”。解决它的一种方法... :-) 我仍然不明白添加如何被视为结构变化,而混合其内容则不是。
  • @Daniel 在 arraylist 源代码中检查 Structurally modified 在代码中的含义。 grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
【解决方案2】:

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

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

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

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

  • ArrayList.modCount 通过所谓的结构修改进行更改
  • ListItr 方法在 init 中复制其值并检查其方法中是否未更改
  • Collections.sort 调用 ListItr.set,后者调用 ArratList.set。最后一种方法不会增加modCount

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

【讨论】:

    【解决方案3】:

    对于 Android,它取决于 API 版本。从 API 26 开始,Collections#sort(List&lt;T&gt;, Comparator&lt;? super T&gt;) 实际上调用了List#sort(Comparator&lt;? super E&gt;)。因此,如果您对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++;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-02-22
      • 2013-02-11
      • 2015-06-19
      • 2018-10-11
      • 2018-11-27
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 2016-04-06
      相关资源
      最近更新 更多