为什么it.next()抛出java.util.ConcurrentModificationException?

时间:2011-08-30 01:42:03

标签: java collections guava multimap concurrentmodification

final Multimap<Term, BooleanClause> terms = getTerms(bq);
        for (Term t : terms.keySet()) {
            Collection<BooleanClause> C = new HashSet(terms.get(t));
            if (!C.isEmpty()) {
                for (Iterator<BooleanClause> it = C.iterator(); it.hasNext();) {
                    BooleanClause c = it.next();
                    if(c.isSomething()) C.remove(c);
                }
            }
        }

不是SSCCE,但你能闻到气味吗?

3 个答案:

答案 0 :(得分:24)

答案 1 :(得分:8)

原因是您正在尝试在迭代器之外修改集合。

工作原理:

创建迭代器时,集合独立地为集合和迭代器维护一个modifyNum变量。 1.对于集合和迭代器的每次更改,收集变量都会递增。 2.对于迭代器的每次更改,迭代器的变量都会递增。

因此,当您通过迭代器调用it.remove()时,会将modification-number-variable的值增加1.

但是当你直接在集合上调用collection.remove()时,它只会增加集合的modification-number变量的值,而不会增加迭代器的变量值。

规则是:只要迭代器的modify-number值与原始集合的modify-number值不匹配,就会产生ConcurrentModificationException。

答案 2 :(得分:3)

Vineet Reynolds详细解释了集合抛出ConcurrentModificationException(线程安全,并发)的原因。 Swagatika详细解释了该机制的实现细节(集合和迭代器如何保持修改次数)。

他们的回答很有趣,我对它们进行了投票。但是,在您的情况下,问题不是来自并发(您只有一个线程),而实现细节虽然有趣,但不应该在这里考虑。

您应该只考虑HashSet javadoc的这一部分:

  

此类的迭代器方法返回的迭代器是快速失败的:   如果在创建迭代器后的任何时间修改了该集,则   除了通过迭代器自己的删除方法,Iterator之外的任何方式   抛出ConcurrentModificationException。因此,面对   并发修改,迭代器快速干净地失败,   而不是冒着任意的,非确定性的行为冒险   未来不确定的时间。

在你的代码中,你使用它的迭代器迭代你的HashSet,但你使用HashSet自己的remove方法来删除元素(C.remove(c)),这会导致ConcurrentModificationException。相反,正如javadoc中所解释的那样,您应该使用Iterator自己的remove()方法,该方法从基础集合中删除当前迭代的元素。

替换

                if(c.isSomething()) C.remove(c);

                if(c.isSomething()) it.remove();

如果您想使用功能更强大的方法,可以在HashSet上创建Predicate并使用Guava的Iterables.removeIf()方法:

Predicate<BooleanClause> ignoredBooleanClausePredicate = ...;
Multimap<Term, BooleanClause> terms = getTerms(bq);
for (Term term : terms.keySet()) {
    Collection<BooleanClause> booleanClauses = Sets.newHashSet(terms.get(term));
    Iterables.removeIf(booleanClauses, ignoredBooleanClausePredicate);
}

PS:请注意,在这两种情况下,这只会从临时HashSet中删除元素。 <{1}}将不会被修改。