Java 迭代器在并发哈希映射中的故障安全性
据我所知,Java 迭代器在并发哈希映射中的故障安全性,java,collections,Java,Collections,据我所知,CopyOnWriteArrayList中的迭代器是线程安全的,因为在创建迭代器时,对arrayList副本的快照引用是创建的,并且在此过程中,所有的变异操作都是(添加、设置等)是通过创建基础阵列的新副本来实现的,因此它们不会影响快照引用引用的副本,对于CopyOnWriteArraySet 但是在ConcurrentHashMap的情况下,您会遇到困难,因此请分享您的观点,在文档中的ConcurrentHaspMap的情况下,迭代器和枚举返回反映迭代器/枚举创建时或创建后某个点的哈希
CopyOnWriteArrayList
中的迭代器是线程安全的,因为在创建迭代器时,对arrayList
副本的快照引用是创建的,并且在此过程中,所有的变异操作都是(添加、设置等)
是通过创建基础阵列的新副本来实现的,因此它们不会影响快照引用引用的副本,对于CopyOnWriteArraySet
但是在ConcurrentHashMap
的情况下,您会遇到困难,因此请分享您的观点,在文档中的ConcurrentHaspMap
的情况下,迭代器和枚举返回反映迭代器/枚举创建时或创建后某个点的哈希表状态的元素
ConcurrentHashMap
生成的迭代器指向迭代器创建时的哈希表数组,因此它们不考虑导致哈希表大小调整的更新,这是他们的规范所允许的。遍历散列存储桶是安全的,因为散列存储桶引用是易变的您的问题有点含糊不清-您在标题中提到了故障保护,但在正文中提到了线程安全。我想你是说线程安全
来自以下位置的样本源:
。。。检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。检索反映了最近完成的更新操作在开始时的结果。对于聚合操作(如putAll和clear),并发检索可能只反映插入或删除某些条目。类似地,迭代器和枚举返回反映哈希表在迭代器/枚举创建时或创建之后某个点的状态的元素。它们不会抛出java.util.ConcurrentModificationException
因此迭代是线程安全的,但他们将契约定义为迭代器,枚举返回反映哈希表状态的元素,在迭代器/枚举创建时或创建之后的某个时刻(至少在概念上,真正的实现要复杂一些)是一个链表数组。通过从第一个节点到最后一个节点逐个遍历链表,可以非常轻松地实现故障安全(非线程安全的)迭代器。
类似这样的方法会奏效:
public boolean hasNext() {
if(next != null) return true;
currentNode = currentNode == null ? null : currentNode.next; // volatile
while(currentNode == null && ++currentIndex < buckets.length) // assume the length is fixed for simplicity
currentNode = buckets[currentIndex].head; // also volatile
if(currentNode != null) next = currentNode.data;
return currentNode != null;
}
public Object next {
if(!hasNext()) throw new NoSuchElementException();
Object result = next;
next = null;
return result;
}
public boolean hasNext(){
if(next!=null)返回true;
currentNode=currentNode==null?null:currentNode.next;//volatile
while(currentNode==null&&++currentIndex
这实际上并不特定于ConcurrentHashMap
,他们也可以用这种方式为常规映射实现它,但选择不这样做。为什么?嗯,因为“常规”映射不是线程安全的,所以同时修改它不是一个好主意,因此,如果发生这种情况,很可能是一个错误,而不是故意发生的,因此,最好在检测到这种情况时“快速失败”,而不是忽略它,然后继续,冒着未来潜在的微妙和难以诊断的不一致的风险
如果你问我是否同意最后一句话,答案是一个响亮的“不”:)但显然,java设计师中有足够多的人,至少在做出这个决定时是这样做的 来自ConcurrentHashMap.java源代码中的代码注释
基本策略是在段之间细分表,每个段本身都是一个并发可读的哈希表 关于Java8 ConcurrentHashMap实现 尽管javadoc,ConcurrentHashMap迭代器并不返回快照,但它在实时并发哈希表上运行,以乐观的方式处理所有并发情况,就像所有无锁数据结构一样 基本上,它包含对当前哈希表、该哈希表中的bin索引和最后返回的节点的引用。 下面是它在不带锁定的并发哈希表上操作时使用的一些技巧:
- 若当前持有的节点已被移除,则跳转到下一个bin(通过增加索引)
- 当哈希表可能处于不一致状态时,乐观的“锁定”(即重试)
- 用于检测重缓存的特殊类型节点(ForwardingNode)
请参阅ConcurrentHashMap.Traverser.advance()方法。ConcurrentHashMap的迭代器是故障安全的,这意味着即使在迭代开始后修改了底层的
ConcurrentHashMap
,它也不会抛出ConcurrentModificationException
。查看,它显示其迭代器扩展了HashIterator(第1251行)它没有任何同步,但允许多个迭代器同时读取映射,并且可以同时更改映射,而不引发任何异常,也不保证返回添加的新元素。所以你可以说ConcurrentHashMap是故障安全的 哇,这是一个很长的句子。需要一些毅力才能到达终点非常有趣的答案。非常感谢。顺便说一句:这个线程表明用于ConcurrentHashMap迭代器的javadoc是不够的。目前尚不清楚(根据官方文件)这究竟是如何运作的。