【问题标题】:Why does this Java code trigger a ConcurrentModificationException?为什么这段 Java 代码会触发 ConcurrentModificationException?
【发布时间】:2021-07-24 21:09:04
【问题描述】:

在循环的第一行我得到了错误,但我不明白为什么。从我读到的内容来看,只有当我迭代一个集合并同时尝试修改它时才会发生这种情况,但事实并非如此。

在代码中,list 的类型为 ArrayList<Product>

void mergeSort() { 
    mergeSort(0, list.size() - 1); //128
}

private void mergeSort(int p, int r) {
    if (p < r) {
        int q = (p + r) / 2;
        mergeSort(p, q); //133
        mergeSort(q + 1, r);
        merge(p, q, r); //135
    }
}

private void merge(int p, int q, int r) {
    List<Product> left = list.subList(p, q);
    left.add(Product.PLUS_INFINITE);
    List<Product> right = list.subList(q + 1, r);
    right.add(Product.PLUS_INFINITE);
    int i = 0;
    int j = 0;
    for (int k = p; k <= r; ++p) {
        Product x = left.get(i).compareTo(right.get(j)) <= 0 ? left.get(i++) : right.get(j++); //147
        list.set(k, x);
    }
}

这是堆栈跟踪:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1415)
    at java.base/java.util.ArrayList$SubList.get(ArrayList.java:1150)
    at ProductList$ProductSort.merge(MainClass.java:147)
    at ProductList$ProductSort.mergeSort(MainClass.java:135)
    at ProductList$ProductSort.mergeSort(MainClass.java:133)
    at ProductList$ProductSort.mergeSort(MainClass.java:128)
    at ProductList.sort(MainClass.java:95)
    at MainClass.main(MainClass.java:187)

【问题讨论】:

  • 获取subList,然后调用add(这是一种结构变化)气味。
  • @azurefrog 是的,Product 只是我写的一个类,不是来自库。

标签: java


【解决方案1】:

subList 不会创建与指定范围内的原始列表具有相同元素的新列表。相反,它创建了一个“视图”(docs):

返回此列表部分的视图 [...]。返回的列表由该列表支持,因此返回列表中的非结构性更改会反映在该列表中,反之亦然。

另请注意:

如果后备列表(即此列表)以除通过返回列表之外的任何方式进行结构修改,则此方法返回的列表的语义将变为未定义。

这正是您在merge 中所做的。您正在创建子列表 left。然后用add在结构上修改left。到目前为止,一切都很好。但随后您创建了另一个子列表 right 并修改了 it。这使得“left 的语义变得未定义”。这会导致对get 的下一次调用引发异常。

最小的可重现示例:

ArrayList<String> list = new ArrayList<>(List.of("1", "2", "3", "4"));
List<String> left = list.subList(0, 2);
List<String> right = list.subList(2, 4);
right.add("5");
left.get(0);

在这方面,子列表有点像迭代器 (you can only remove via the iterator, if you remove via the original list, CME might be thrown)。

解决此问题的一种简单方法是创建子列表的副本,以便它们不再是“视图”,而是实际上是独立的列表:

List<Product> left = new ArrayList<>(list.subList(p,q));
List<Product> right = new ArrayList<>(list.subList(q+1,r));

【讨论】:

  • 感谢您的信息。我尝试了这个解决方案,但错误仍然存​​在于相同的堆栈跟踪中。
  • @gian_ 不应该再有 CME,但我发现您的合并排序算法实现不正确。您可能打算在 for 循环中增加 k 而不是 p,并且您的索引似乎有点混乱。 sublist 的第二个参数是独占的,而第一个是包含的。所以subList(p,q)subList(q+1,r) 完全跳过q,而忽略r。你可能还有其他错误,这只是我看到的几个。
  • 糟糕,是的,我的意思是增加k,但我不知道subList 的第二个参数是独占的。通过这两个修复,您建议代码现在可以工作,并且 CME 异常消失了。谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 1970-01-01
  • 2018-07-28
相关资源
最近更新 更多