我有一个由线程A读取和更新的同步Map(通过Collections.synchronizedMap()
)。线程B仅通过Map.keySet()
(只读)访问Map。
我应该如何同步? keySet()的docs say(对于Collections.synchronizedMap)“不需要在同步块中”。我可以将Thread A的读/写访问权限放在同步块中,但这是否必要?
对于我来说,如果Map.keySet不需要同步(根据上面的文档链接),我甚至使用同步Map或同步块似乎很奇怪......
更新:我错过了必须同步keySet的迭代,即使检索keySet不需要同步。拥有keySet而不能查看它并不是特别令人兴奋,因此最终结果=需要同步。改为使用ConcurrentHashMap。
答案 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()
)会产生有用的并发行为(“弱一致”迭代器)。您将在迭代时遍历集合状态中的所有键,并且在创建迭代器后可能会看到或未看到更改。