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是不够的。目前尚不清楚(根据官方文件)这究竟是如何运作的。