The Iterator
为了HashSet
类是一个快速失败迭代器。从文档中HashSet class:
此类的迭代器方法返回的迭代器是快速失败的:
如果在创建迭代器后随时修改集合,则
除了通过迭代器自己的删除方法之外的任何方式,迭代器
抛出 ConcurrentModificationException。于是,面对
并发修改,迭代器快速而干净地失败,
而不是冒着任意、非确定性行为的风险
未来不确定的时间。
请注意,无法保证迭代器的快速失败行为
因为一般来说,不可能做出任何硬性保证
存在不同步的并发修改。快速失败
迭代器尽力抛出 ConcurrentModificationException
基础。因此,编写依赖于
关于此异常的正确性:快速失败行为
迭代器应该仅用于检测错误。
注意最后一句话——事实上你正在抓住一个ConcurrentModificationException
意味着另一个线程正在修改集合。同一个 Javadoc API 页面还指出:
如果多个线程同时访问一个哈希集,并且至少一个
线程修改集合,它必须在外部同步。
这通常是通过同步某个对象来完成的
自然地封装了集合。如果不存在这样的对象,则设置
应该使用“包裹”Collections.synchronizedSet方法。这
最好在创建时完成,以防止意外不同步
访问集合:
Set s = Collections.synchronizedSet(new HashSet(...));
我相信对 Javadoc 的引用对于下一步应该做什么是不言自明的。
此外,就您而言,我不明白您为什么不使用ImmutableSet,而不是在terms
对象(可能会在此期间进行修改;我看不到该对象的实现getTerms
方法,但我有预感底层键集正在被修改)。创建不可变集将允许当前线程拥有原始键集的自己的防御副本。
请注意,虽然ConcurrentModificationException
可以通过使用同步集来防止(如 Java API 文档中所述),前提是所有线程访问同步集合而不是直接访问后备集合(在您的情况下这可能是不正确的,因为HashSet
可能是在一个线程中创建的,而底层集合MultiMap
被其他线程修改)。同步集合类实际上维护一个内部互斥锁,供线程获取访问权限;由于您无法直接从其他线程访问互斥体(在这里这样做是非常荒谬的),因此您应该考虑使用键集或 MultiMap 本身的防御性副本使用unmodifiableMultimap的方法MultiMaps class(您需要从 getTerms 方法返回一个不可修改的 MultiMap)。您还可以调查退回的必要性同步多图,但话又说回来,您需要确保任何线程都必须获取互斥锁,以保护底层集合免受并发修改的影响。
请注意,我故意省略了提及线程安全HashSet唯一的原因是我不确定是否能确保对实际集合的并发访问;但情况很可能并非如此。
Edit: ConcurrentModificationException
被扔在Iterator.next
在单线程场景下
这是关于以下声明:if(c.isSomething()) C.remove(c);
这是在编辑的问题中引入的。
调用Collection.remove
改变了问题的性质,因为现在有可能ConcurrentModificationException
即使在单线程场景中也会抛出。
这种可能性是由于方法本身的使用以及与Collection
的迭代器,在本例中为变量it
这是使用以下语句初始化的:Iterator<BooleanClause> it = C.iterator();
.
The Iterator
it
迭代Collection
C
存储与当前状态相关的状态Collection
。在这种特殊情况下(假设有 Sun/Oracle JRE),KeyIterator
(一个内部内部类HashMap
使用的类HashSet
) 用于迭代Collection
。这个的一个特殊的特点是Iterator
是它跟踪对结构进行的修改数量Collection
(the HashMap
在这种情况下)通过它的Iterator.remove
method.
当你调用remove
on the Collection
直接,然后调用Iterator.next
,迭代器抛出一个ConcurrentModificationException
, as Iterator.next
验证是否有任何结构修改Collection
已经发生了Iterator
是不知道的。在这种情况下,Collection.remove
引起结构修改,该修改由Collection
,但不是由Iterator
.
要解决这部分问题,您必须调用Iterator.remove
并不是Collection.remove
,因为这确保了Iterator
现在已经知道对Collection
. The 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.
}
}
}