【问题标题】:ConcurrentModificationException: .add() vs .addAll()ConcurrentModificationException:.add() 与 .addAll()
【发布时间】:2014-10-03 18:20:27
【问题描述】:

为什么会出现以下情况?两者都不应该工作吗?

List<String> items = data;
for( String id : items ) {
    List<String> otherItems = otherData;        

    // 1.   addAll()
    //Causes ConcurrentModificationException
    items.addAll(otherItems);

    // 2.   .add()
    //Doesn't cause exceptions
    for( String otherId : otherItems ) {
        items.add(otherId);
    }
}

是不是因为 add() 添加到集合 Items 中,但 addAll() 创建了一个新集合从而将 Items 修改为 List 的不同实例?

编辑 itemsotherItems 是具体类型 ArrayList&lt;String&gt;

【问题讨论】:

  • Items具体是什么类型?
  • 因为您在项目上打开了一个迭代器,并且项目不支持并发修改。为什么你被允许一次添加是真正的问题。不管怎样,你在这里做的可能不是你想要的。

标签: java foreach concurrentmodification


【解决方案1】:

这两种操作都不正确,因为它在迭代时修改了集合。

检查the implementation of ArrayList 表明调用addaddAll 应该在下一次循环迭代中成功抛出ConcurrentModificationException。对于add,它没有这样做的事实意味着对于您拥有的特定Java版本的ArrayList类中存在一个模糊的错误;或者(更有可能)otherItems 为空,因此在第二种情况下,您实际上根本没有调用 add

我确定otherItems 一定是空的,因为如果添加到Items 列表以您想要的方式“工作”,那么它会在每次循环中增长,导致它无限循环直到死亡OutOfMemoryError。

【讨论】:

  • 你是对的,otherItems 从未被填充。如果里面有数据,它确实会失败,很好!
  • @FreakyDan - 这意味着“其他项目”也永远不会导致 CME。你问题中的代码搞砸了。 addAll() 的空集合不会导致 CME。
  • @FreakyDan - 实际上,您可能在 Java 中发现了一个错误……嗯。我正在编辑我的答案。
  • @rolfl addAll 的空集合确实会导致 CME。参见ArrayList代码:addAll无条件调用ensureCapacityInternal,后者无条件调用ensureExplicitCapacity,后者无条件调用modCount++
  • 我现在看到了这种副作用。这是一个错误(在 ArrayList 中)。很有趣。
【解决方案2】:

你在这里的 for 循环

for( String id : Items ) {

逻辑上等同于:

for(Iterator<String> it = Items.iterator(); it.hasNext();) {
    String id = it.next();

    ....
}

现在,如果您修改迭代器迭代的列表,在迭代过程中,您会得到 ConcurrentModificationException。来自Javadoc for ArrayList

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时候对列表进行结构修改,除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器将抛出一个 ConcurrentModificationException。

所以,addAll() 正在修改 Items,并导致迭代器失败。

您唯一的解决方案是:

  1. 不要执行 addAll()
  2. 用迭代器切换到循环,然后做:

    for (String other : otherItems) {
        it.add(other);
    }
    

    换句话说,通过迭代器进行添加,并避免 ConcurrentModification....

现在,关于为什么 add() 有效,而 addAll() 无效?我相信您可能只是在迭代器中没有其他项目时看到类似添加版本的内容,或者正在添加的值是空的,也许是项目实现中的错误。它应该抛出一个 CME,而它没有抛出一个事实意味着存在一个错误,不是在您的代码中,而是在集合中。

他们都应该失败!

但是:您随后发现addAll() 正在添加一个空集合。一个空的 addAll() 不应该导致 CME ......而且,正如@Boann 所指出的,这是 ArrayList 实现中的一个错误。

我整理了以下测试来证明这一点:

private static List<String> buildData() {
    return new ArrayList<>(Arrays.asList("Hello", "World"));
}

public static void testThings(List<String> data, List<String> addall, List<String> add) {
    System.out.printf("Using %s addAll %s and add %s%n", data, addall, add);

    try {
        for (String s : data) {
            if (addall != null) {
                data.addAll(addall);
            }
            if (add != null) {
                for (String a : add) {
                    data.add(a);
                }
            }
        }
        System.out.println("OK: " + data);
    } catch (Exception e) {
        System.out.println("Fail: " + e.getClass() + " -> " + e.getMessage());
    }
}

public static void main(String[] args) {

    String[] hw = {"Hello", "World"};

    testThings(buildData(), Arrays.asList(hw), null);
    testThings(buildData(), null, Arrays.asList(hw));
    testThings(new ArrayList<>(), Arrays.asList(hw), null);
    testThings(new ArrayList<>(), null, Arrays.asList(hw));
    testThings(buildData(), new ArrayList<>(), null);
    testThings(buildData(), null, new ArrayList<>());
    testThings(new ArrayList<>(), new ArrayList<>(), null);
    testThings(new ArrayList<>(), null, new ArrayList<>());
}

这会产生结果:

Using [Hello, World] addAll [Hello, World] and add null
Fail: class java.util.ConcurrentModificationException -> null
Using [Hello, World] addAll null and add [Hello, World]
Fail: class java.util.ConcurrentModificationException -> null
Using [] addAll [Hello, World] and add null
OK: []
Using [] addAll null and add [Hello, World]
OK: []
Using [Hello, World] addAll [] and add null
Fail: class java.util.ConcurrentModificationException -> null
Using [Hello, World] addAll null and add []
OK: [Hello, World]
Using [] addAll [] and add null
OK: []
Using [] addAll null and add []
OK: []

注意这两行:

Using [Hello, World] addAll [] and add null
Fail: class java.util.ConcurrentModificationException -> null
Using [Hello, World] addAll null and add []
OK: [Hello, World]

添加一个空的 addAll 会导致 CME,但这不会在结构上修改列表。这是 ArrayList 中的一个错误。

【讨论】:

  • 有趣,我一直认为你可以添加到最后是故意的。我一直认为它可以添加到链中,但不能重新排列。所以在我缺乏经验的头脑中,两者都应该起作用。
  • 我喜欢这样的“现在,如果您修改迭代器迭代的列表,在迭代过程中,您会得到 ConcurrentModificationException。”你有什么资源可以让我阅读更多吗?
  • @KickButtowski - JavaDoc for ArrayList此类的迭代器和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间对列表进行了结构修改,在除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器都会抛出 ConcurrentModificationException。
  • 对于是否应该在 nasNext()next() 或这两个调用上抛出 CME 的规范是松散的。 Java 库只抛出 next() 而不是 hasNext(),有些人认为这是一个错误......因为这不是“快速失败”。
  • 感谢您的链接,我的假设是 ArrayList.add() 的工作方式类似于 'iterator's own [add method]' 而 .addAll() 则不然。
【解决方案3】:

我同意 rolfl -> 在这两种情况下都应该抛出 CME...

最可能的答案:

addAll() 是第一次调用,它抛出异常,而第二段代码根本就没有到达 -> 您忘记将第一部分注释掉,因此认为 add()-part 是无异常代码;)

【讨论】:

    猜你喜欢
    • 2020-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    相关资源
    最近更新 更多