为什么.next()抛出java.util.ConcurrentModificationException? final Multimap terms=getTerms(bq); for(Term t:terms.keySet()){ 集合C=新哈希集(terms.get(t)); 如果(!C.isEmpty()){ for(Iterator it=C.Iterator();it.hasNext();){ boolean子句c=it.next(); 如果(c.isSomething())c.移除(c); } } }
不是SSCCE,但是你能闻到气味吗?类的为什么.next()抛出java.util.ConcurrentModificationException? final Multimap terms=getTerms(bq); for(Term t:terms.keySet()){ 集合C=新哈希集(terms.get(t)); 如果(!C.isEmpty()){ for(Iterator it=C.Iterator();it.hasNext();){ boolean子句c=it.next(); 如果(c.isSomething())c.移除(c); } } },java,collections,guava,multimap,concurrentmodification,Java,Collections,Guava,Multimap,Concurrentmodification,不是SSCCE,但是你能闻到气味吗?类的迭代器是一个快速失败的迭代器。从课程的文档中: 此类的迭代器方法返回的迭代器是fail fast: 如果在创建迭代器后的任何时间修改集合,则在 除了通过迭代器自己的remove方法,迭代器 抛出ConcurrentModificationException。因此,面对 并发修改,迭代器快速、干净地失败, 而不是冒着一种随意的、不确定的行为的风险 未来时间未定 请注意,无法保证迭代器的快速失败行为 一般来说,不可能作出任何硬性保证 在存在非同步并发修改的情况
迭代器是一个快速失败的迭代器。从课程的文档中:
此类的迭代器方法返回的迭代器是fail fast:
如果在创建迭代器后的任何时间修改集合,则在
除了通过迭代器自己的remove方法,迭代器
抛出ConcurrentModificationException。因此,面对
并发修改,迭代器快速、干净地失败,
而不是冒着一种随意的、不确定的行为的风险
未来时间未定
请注意,无法保证迭代器的快速失败行为
一般来说,不可能作出任何硬性保证
在存在非同步并发修改的情况下。快速失败
迭代器尽最大努力抛出ConcurrentModificationException
基础。因此,编写依赖于
关于其正确性的例外:的fail fast行为
迭代器只能用于检测bug
请注意最后一句话-您正在捕获一个ConcurrentModificationException
表示另一个线程正在修改集合。同一Javadoc API页面还声明:
如果多个线程同时访问哈希集,并且至少有一个
修改集合的线程数,必须在外部同步。
这通常是通过在某个
自然地封装了集合。如果不存在此类对象,则设置
应该使用该方法“包装”。这
最好在创建时执行,以防止意外的不同步
访问集合:
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);
}
}
}
我相信对Javadoc的引用对于下一步应该做什么是不言自明的
此外,在您的情况下,我不明白您为什么不使用,而不是在terms
对象上创建HashSet(这可能会在此期间被修改;我看不到getTerms
方法的实现,但我有一种预感,底层的键集正在被修改)。创建一个不可变集将允许当前线程拥有它自己的原始密钥集的防御副本
请注意,尽管可以通过使用同步集(如Java API文档中所述)来防止出现ConcurrentModificationException
,但所有线程都必须直接访问同步集合,而不是备份集合(在您的情况下,这可能是不正确的,因为HashSet
可能是在一个线程中创建的,而MultiMap
的基础集合是由其他线程修改的)。同步收集类实际上为线程维护了一个内部互斥体以获取访问权限;因为您不能直接从其他线程访问互斥体(在这里这样做是非常可笑的),所以您应该考虑使用密钥集或多重映射本身的防御副本(您需要从getTerms方法返回一个不可修改的MultiMap)。您还可以研究返回的必要性,但同样,您需要确保任何线程都必须获取互斥,以保护基础集合不受并发修改的影响
请注意,我故意忽略了对的使用,唯一的原因是我不确定是否能够确保对实际集合的并发访问;很可能不是这样
编辑:ConcurrentModificationException
s在Iterator上抛出。下一步在单线程场景中
这是关于在编辑的问题中引入的语句:if(c.isSomething())c.remove(c);
调用Collection.remove
会改变问题的性质,因为现在即使在单线程场景中也可以抛出ConcurrentModificationException
s
这种可能性产生于方法本身的使用,以及集合的迭代器的使用,在这种情况下,变量it
使用以下语句初始化:iterator it=C.iterator();
迭代集合的迭代器它
存储与集合的当前状态相关的状态
(由HashSet
使用的HashMap
类的内部类)用于迭代集合
。此迭代器
的一个特殊特征是它跟踪对集合执行的结构修改的数量(本例中的HashMap
)通过它的迭代器。删除方法
当您直接调用集合
上的删除
时,然后再调用迭代器。下一步
,迭代器将抛出一个ConcurrentModificationException
,作为迭代器。下一步
验证是否对集合
进行了任何结构修改,以确保迭代器
不知道。在这种情况下,Collection.remove
会导致结构修改,该修改由集合
跟踪,而不是由迭代器
跟踪
为了克服这个问题
Set s = Collections.synchronizedSet(new HashSet(...));
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.
}
}
}
if(c.isSomething()) C.remove(c);
if(c.isSomething()) it.remove();
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);
}