【问题标题】:How to synchronize Map between one r/w Thread and one read-only Thread?如何在一个读/写线程和一个只读线程之间同步映射?
【发布时间】:2012-08-18 06:54:06
【问题描述】:

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

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

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

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

【问题讨论】:

  • @tuğrulbüyükışık 不。一般 Java 并发问题。

标签: java multithreading data-structures map synchronized


【解决方案1】:

要创建真正的读/写与只读锁定Map 包装器,您可以查看Collections 用于synchronizedMap() 的包装器,并将所有synchronized 语句替换为@987654326 @。这是一项很好的工作。相反,您应该考虑改用 ConcurrentHashMap,它可以在那里做所有正确的事情。

keySet() 而言,它不需要位于synchronized 块中,因为它已经被Collections.synchronizedMap() 定位为synchronized。 Javadocs 只是指出,如果您正在遍历地图,则需要在其上进行同步,因为您正在执行多个操作,但是当您获得包裹在 @ 中的 keySet() 时不需要同步987654333@ 自己同步的类。

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

【讨论】:

  • 我想我知道你想说什么,但keySet() 不是Map 中的密钥副本”(这是现场直播视图),并且必须不是必须在同步块中。如果它确实是一个副本并且您没有在同步块中获得它,那么您的迭代可能是过时的数据。为确保它是新鲜的,您必须在同步中包含 keySet() 方法。
  • 我知道它通常是@Mark,但我没有看代码。现在我看到它把它包装在SynchronizedSet 中。我会改变我的答案。谢谢。
  • 所以,澄清一下,如果我使用 ConcurrentHashMap,我是否还需要同步访问(读/写或只读),还是由 CHM 本身处理同步?而且,在每种情况下,锁使用什么对象?
  • 如果你使用ConcurrentHashMap,你不需要做任何同步。班级会做这一切。它从一开始就被设计成一个高性能的可重入地图@eric。
  • 优秀。感谢您的澄清。并且回复:您对即使对于只读情况进行同步的编辑,我理解(经过一些 SO 挖掘)只读并不意味着不需要同步。主要是我错过了文档提出的迭代需要同步的观点(请参阅我对@MarkPeters 答案的评论)。将编辑我的问题以反映这一点。
【解决方案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实时视图。如果映射中的键在获取的键集的引用和同步块的开始之间发生变化,则键集将反映这些变化。

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

【讨论】:

  • 很好,我错过了keySet的迭代必须同步。
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 2012-07-04
    • 2023-03-19
    • 2015-11-02
    • 2015-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多