同步映射的键集上的线程安全迭代

时间:2019-02-15 21:20:30

标签: java multithreading collections

在我的多线程代码中的某处,有一个这样声明的映射(可被多个线程访问):

private Map<Foo, Integer> fooMap =
    Collections.synchronizedMap(new HashMap<Foo, Integer>());

同一类公开一个公共方法

public Collection<Foo> getFooList() {
    return fooMap.keySet();
}

在代码的其他地方,我遍历了getFooList()返回的集合。我知道,同步映射上的大多数操作都是线程安全的,一个值得注意的例外是迭代,必须明确地对其进行同步。我已经通过以下方式实现了这一点:

synchronized(bar.getFooList()) {
    for (Foo foo : bar.getFooList()) {
        // do stuff with foo
    }
}

我偶尔会为ConcurrentModificationException语句得到一个for。我想知道是否要与错误的类实例同步-我应该与映射而不是其键集进行同步吗?再说一次,我真的不想将整个地图公开给其他类(出于某种原因它是私有的)。

如何以线程安全的方式遍历键集,而不必暴露整个映射?

1 个答案:

答案 0 :(得分:3)

来自Javadoc

  

用户必须手动对返回的内容进行同步   遍历其任何集合视图时进行映射:

  Map m = Collections.synchronizedMap(new HashMap());
      ...
  Set s = m.keySet();  // Needn't be in synchronized block
      ...
  synchronized (m) {  // Synchronizing on m, not s!
      Iterator i = s.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }
     

不遵循此建议可能会导致不确定性   行为。如果指定的地图,则返回的地图将可序列化   可序列化。

您可以使用ConcurrentHashMap来保证keySet上的并发性。参见here

  

视图的迭代器是一个“弱一致性”迭代器,它将永远不会   抛出ConcurrentModificationException,并保证遍历   在构造迭代器时存在的元素,并且可能   (但不能保证)反映出之后的任何修改   施工。