如何在一个r / w线程和一个只读线程之间同步Map?

时间:2012-08-22 19:26:49

标签: java multithreading data-structures map synchronized

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

我应该如何同步? keySet()的docs say(对于Collections.synchronizedMap)“不需要在同步块中”。我可以将Thread A的读/写访问权限放在同步块中,但这是否必要?

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

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

3 个答案:

答案 0 :(得分:2)

要创建一个真正的读/写与只读/锁定Map包装器,您可以查看Collections用于synchronizedMap()的包装器并替换所有synchronized带有ReentrantReadWriteLock的语句。这是一项很好的工作。相反,您应该考虑切换到使用ConcurrentHashMap来完成所有正确的事情。

keySet()而言,它不需要位于synchronized块中,因为synchronized已经Collections.synchronizedMap()。 Javadocs只是指出,如果你在迭代地图,你需要在它上面进行同步,因为你正在做多个操作,但是当你得到包含在其中的keySet()时你不需要同步一个SynchronizedSet类,它自己进行同步。

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

答案 1 :(得分: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()在同步块中是无关紧要的,因为它是{{1>}的实时视图 }。如果映射中的键在获取的键集的引用和同步块的开头之间发生变化,则键集将反映这些更改。

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

答案 2 :(得分:2)

文档是正确的。从Collections.synchronizedMap()返回的地图将正确地围绕发送到原始Map的所有呼叫进行同步。但是,keySet()返回的set impl没有相同的属性,因此必须确保在同一个锁下读取它。

如果没有此同步,则无法保证线程B将看到线程A进行的任何更新。

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