在我的多线程代码中的某处,有一个这样声明的映射(可被多个线程访问):
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
。我想知道是否要与错误的类实例同步-我应该与映射而不是其键集进行同步吗?再说一次,我真的不想将整个地图公开给其他类(出于某种原因它是私有的)。
如何以线程安全的方式遍历键集,而不必暴露整个映射?
答案 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,并保证遍历 在构造迭代器时存在的元素,并且可能 (但不能保证)反映出之后的任何修改 施工。