6

我有一个Collections.synchronizedMap()由线程 A 读取和更新的同步映射(通过)。线程 B 仅通过Map.keySet()(只读)访问映射。

我应该如何同步这个?文档说keySet() (对于Collections.synchronizedMap)“不需要在同步块中”。我可以将线程 A 的读/写访问权限放在同步块中,但这甚至有必要吗?

如果 Map.keySet 不需要同步(根据上面的文档链接),我想我什至使用同步 Map 或同步块似乎很奇怪......

更新:我错过了 keySet 的迭代必须同步,即使检索 keySet 不需要同步。拥有 keySet 而无法查看它并不是特别令人兴奋,因此最终结果 = 需要同步。改用 ConcurrentHashMap。

4

3 回答 3

3

要制作一个真正的读/写与只读/只读锁定Map包装器,您可以查看包装器的Collections用途synchronizedMap()并将所有synchronized语句替换为ReentrantReadWriteLock. 这是一项很好的工作。相反,您应该考虑切换到使用 a ConcurrentHashMapwhich 做所有正确的事情。

就 而言keySet(),它不需要在一个synchronized块中,因为它已经synchronizedCollections.synchronizedMap(). Javadocs 只是指出,如果您正在遍历地图,则需要在其上进行同步,因为您正在执行多个操作,但是当您获取keySet()包装在SynchronizedSet执行它的类中时不需要同步自己的同步。

最后,您的问题似乎暗示如果您只是从中读取内容,则不需要同步某些内容。您必须记住,同步不仅可以防止竞争条件,还可以确保每个处理器正确共享数据。即使您Map以只读方式访问 a,如果任何其他线程正在更新它,您仍然需要对其进行同步。

于 2012-08-22T19:31:03.230 回答
2

文档告诉您如何正确同步需要原子的多步操作,在这种情况下迭代地图:

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

请注意实际迭代必须如何在同步块中。文档只是说,如果在同步块中获得并不重要keySet(),因为Map. 如果映射中的键在对正在获取的键集的引用和同步块的开始之间发生变化,则键集将反映这些变化。

顺便说一句,您引用的文档仅适用Map于由Collections.synchronizedMap. 该声明不一定适用于所有Maps。

于 2012-08-22T19:31:48.083 回答
2

文档是正确的。从返回的映射Collections.synchronizedMap()将正确包装同步发送到原始的所有调用Map。但是,keySet() 返回的集合 impl 不具有相同的属性,因此您必须确保在同一锁下读取它。

如果没有这种同步,就不能保证线程 B 会看到线程 A 所做的任何更新。

您可能想要调查ConcurrentHashMap. 它为这个用例提供了有用的语义。在 CHM ( likekeySet() )中迭代集合视图会提供有用的并发行为(“弱一致”迭代器)。您将在迭代时遍历集合状态中的所有键,并且您可能会或可能不会在迭代器创建后看到更改。

于 2012-08-22T19:37:08.923 回答