当前位置: 首页 > 面试题库 >

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

呼延升
2023-03-14
问题内容

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


不是SSCCE,但您可以闻到气味吗?


问题答案:

IteratorHashSet类是快速失败的迭代器。从HashSet该类的文档中:

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

注意,不能保证迭代器的快速失败行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。

注意最后一句话-您正在捕获一个事实,ConcurrentModificationException意味着另一个线程正在修改该集合。同一Javadoc
API页面还指出:

如果多个线程同时访问哈希集,并且至少有一个线程修改了哈希集,则必须在外部对其进行同步。通常,通过在自然封装了该集合的某个对象上进行同步来实现。如果不存在这样的对象,则应使用Collections.synchronizedSet方法将其“包装”
。最好在创建时完成此操作,以防止意外地异步访问集合:

Set s = Collections.synchronizedSet(new HashSet(...));

我相信对Javadoc的引用在接下来应该做什么方面具有自我解释性。

此外,在您的情况下,我看不到您为什么不使用ImmutableSet,而不是在terms对象上创建HashSet的原因(可以在此期间进行修改;我看不到该getTerms方法的实现,但我有一种预感基础键集正在被修改)。创建一个不可变的集合将允许当前线程拥有其自己的原始密钥集的防御性副本。

请注意,尽管ConcurrentModificationException可以通过使用同步集(如Java
API文档中所述)来防止a,但这是所有线程都直接访问同步集合而不是后备集合的先决条件(在您的情况下,这可能是不正确的)HashSet是在一个线程中创建的,而的基础集合MultiMap则由其他线程修改了)。同步的收集类实际上维护了一个内部互斥体,线程可以获取该互斥体。由于您不能直接从其他线程访问互斥锁(在这里这样做是很荒谬的),因此应该使用类的方法来研究使用键集或MultiMap本身的防御性副本unmodifiableMultimap``MultiMaps(您需要从getTerms方法返回一个不可修改的MultiMap)。您还可以研究返回同步MultiMap的必要性,但是再次,您需要确保任何线程都必须获取互斥量,以保护基础集合免受并发修改。

注意,由于我不确定是否可以确保对实际集合的并发访问,我故意省略了线程安全HashSet的使用。很有可能不是这种情况。

编辑: 在单线程方案中ConcurrentModificationException抛出Iterator.next

这是针对以下陈述:if(c.isSomething()) C.remove(c);在已编辑的问题中引入的。

调用Collection.remove改变了问题的性质,因为ConcurrentModificationException即使在单线程情况下,也有可能引发该问题。

可能是由于方法本身的使用以及Collection迭代器的使用,在这种情况下,该变量it是使用语句:初始化的Iterator<BooleanClause> it = C.iterator();

Iterator it该迭代Collection C门店状态相关的的当前状态Collection。在这种特殊情况下(假定为Sun /
Oracle
JRE),使用KeyIteratorHashMap由所使用的类的内部内部类HashSet)来迭代Collection。它的一个特殊特征Iterator是它通过其方法跟踪对CollectionHashMap在这种情况下)进行的结构修改的次数Iterator.remove

当您直接调用,然后调用remove进行Collection后续操作时Iterator.next,迭代器将抛出ConcurrentModificationException,作为Iterator.next验证是否Collection发生了对的任何结构修改,而这些变化Iterator是您不知道的。在这种情况下,Collection.remove会导致结构上的修改,该修改由Collection而不是跟踪Iterator

要解决问题的这一部分,您必须调用Iterator.remove而不是Collection.remove,因为这确保了Iterator现在知道了对的修改Collection。在Iterator这种情况下,将跟踪通过发生的结构修饰remove方法。因此,您的代码应如下所示:

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()) it.remove(); // <-- invoke remove on the Iterator. Removes the element returned by it.next.
                }
            }
        }


 类似资料:
  • 问题内容: 我试图将两个’Employee’对象添加到TreeSet中: 但是它抛出一个ClassCastException: 但是,如果我仅将一个对象添加到TreeSet中: 或者,如果我改用HashSet: 那就成功了。为什么会发生异常,我该如何解决? 问题答案: 要么必须实现,或者你需要提供一个比较创建时。 在文档中对此进行了详细说明: 插入排序集中的所有元素都必须实现接口(或被指定的比较器

  • 问题内容: 将字符串解析为字节时出现异常 问题答案: 这是因为默认的parse方法要求使用十进制格式的数字来解析十六进制数字,请使用以下parse: 其中16是解析的基础。 至于您的评论,您是对的。字节的最大值为0x7F。因此,您可以将其解析为并执行二进制与操作以获得LSB,即您的字节:

  • 问题内容: 我有这种方法: 映射: Person.hbm.xml Cars.hbm.xml 此方法适用于单个线程,并且在多个线程上,给我一个错误: AOP交易: 注意:当我在更新后添加Thread.sleep(5000)时,就可以了。 但是这种解决方案并不干净。 问题答案: 我有汽车->(1-n)个地方。而且我在表位置(id_car)有一个外键。此外键没有索引。当我向该外键添加索引时,我的问题已解

  • 当使用Mockito截尾时,我们通常为一个方法指定一个具体的参数。之后,我们可以用相同的参数调用Stubed方法。例如,在下面的代码中,get()用参数0进行了stubed。所以,当调用get()时,get()的参数也是0。因此,get()将返回一个整数,即3。 如果调用语句被修改为: 即参数更改为1,然后Mockito抛出“NullPointerException”,而不是“AssertionE

  • 下面是我的代码: 现在我的脑海中应该不会出现不匹配,因为显然提取了两个整数?!

  • 问题内容: 根据文档,该方法返回: 如果参数是,则字符串等于; 否则,返回的值。 但是,当我尝试这样做时,怎么会这样: 它会抛出NPE吗?(如果你不相信,请自己尝试!) 怎么会这样呢?文件在骗我吗?这是Java中的主要错误吗? 问题答案: 问题是方法已重载: String.valueOf(Object) String.valueOf(char[]) Java规范语言要求在这种情况下,选择最具体的重