迭代器如何在并发哈希映射中进行故障保护

时间:2015-01-07 18:25:57

标签: java collections

据我所知,CopyOnWriteArrayList中的迭代器是线程安全的,因为在创建迭代器时对arrayList的副本进行了快照引用,并且在此所有变异操作(add, set, and so on)都是通过制作基础数组的新副本来实现的,因此它们不会影响快照引用引用的副本,CopyOnWriteArraySet也是如此,

但在ConcurrentHashMap的情况下却很挣扎,所以请在ConcurrentHaspMap

的情况下分享您对迭代器的故障安全性的看法

6 个答案:

答案 0 :(得分:2)

您的问题有点含糊不清 - 您在标题中提及failsafe,但在正文中提及thread-safe。我假设你的意思是线程安全的

来自GrepCode

的示例来源
  

...检索操作(包括get)一般不会阻塞,因此可能与更新操作重叠(包括put和remove)。检索反映了最近完成的更新操作的结果。对于诸如putAll和clear之类的聚合操作,并发检索可能反映仅插入或删除某些条目。类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素。它们不会抛出java.util.ConcurrentModificationException。

所以迭代是线程安全的,但是它们将契约定义为 Iterators和Enumerations返回反映哈希表状态的元素,在迭代器/枚举创建时或之后的某个时刻。

答案 1 :(得分:0)

从文档:“类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素。”

ConcurrentHashMap生成的迭代器指向创建迭代器时的哈希表数组,因此它们不考虑导致哈希表调整大小的更新,这是其规范所允许的。迭代散列桶是安全的,因为散列桶引用是volatile

答案 2 :(得分:0)

好吧,所有的哈希映射(至少在概念上,实际的实现有点复杂)是一个链表的数组。通过从第一个节点到最后一个节点逐个遍历链接列表,可以非常简单地实现故障安全(非线程安全)迭代器。 像这样的东西会起作用:

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;
}

这并不是ConcurrentHashMap所特有的,他们可以通过这种方式为常规地图实现它,但选择不这样做。为什么?好吧,因为&#34;常规&#34; map不是线程安全的,同时修改它不是一个好主意,因此,如果发生这种情况,很可能是一个错误而不是故意发生,因此,最好快速失败&#34;一旦检测到这种情况而不是忽视它,并继续进行,冒着潜在的微妙和难以诊断未来的不一致的风险。

如果你问我是否同意这最后的陈述,答案是响亮的&#34; no&#34; :)但显然,java设计师中有足够多的人,至少在做出这个决定时回来了。

答案 3 :(得分:0)

来自ConcurrentHashMap.java源代码中的代码注释。

基本策略是在Segments中细分表,每个表本身都是一个可同时读取的哈希表。

答案 4 :(得分:0)

关于Java8 ConcurrentHashMap实现。

尽管使用了javadoc,但ConcurrentHashMap Iterator并不返回快照,它以实时并发哈希表的形式运行,乐观地处理所有并发情况,就像所有无锁数据结构一样。

基本上,它包含对当前哈希表的引用,该哈希表中的bin索引以及最后返回的Node。 以下是一些用于操作并发哈希表的一些技巧:

  • 如果当前保存的节点已被删除,请跳转到下一个bin(通过递增索引)
  • 当哈希表处于不一致状态时,乐观'锁定'(即再试一次)
  • 检测重新散列的特殊类型的节点(ForwardingNode)

参见ConcurrentHashMap.Traverser.advance()方法。

答案 5 :(得分:0)

ConcurrentHashMap的迭代器是故障安全,这意味着即使在迭代开始后修改了基础ConcurrentModificationException,它也不会抛出ConcurrentHashMap。查看java code,它显示其迭代器扩展 HashIterator 1251行),它没有任何同步,但它允许多个迭代器同时读取地图,同时可以更改地图而不会抛出任何异常,并且不保证保证将返回添加的新元素。所以你可以说ConcurrentHashMap是故障安全