【问题标题】:Thread safe way to copy a map复制地图的线程安全方式
【发布时间】:2014-09-11 15:51:38
【问题描述】:

我正在使用 JDK 7、SQLite,并在我的项目中使用 Guava。

我有一个少于 100 个条目的 TreeMap,它由单个“工作”线程每秒更新数百次。我现在正在编写一个组件(另一个线程 - “DB 线程”),它将每隔 5 或 10 秒将地图写入我的数据库。

我知道我需要制作地图的深层副本,以便 DB 线程使用快照,而工作线程继续其工作。我正在查看Guava Maps class,它有许多制作副本的方法,但我不确定它们是否满足我在需要副本时在地图上同步的需求。有没有一种方法可以满足我的需求,或者我应该写一个同步块来制作我自己的深拷贝?

【问题讨论】:

  • 你需要一个同步块。并且工作线程也必须在同一个锁上同步。
  • 除非您可以修改工作线程以兑现某种锁(在您制作副本时将它们排除在外),否则这是行不通的。但是工作线程肯定已经内置了某种锁定机制,所以您需要做的就是将它也用于复制。
  • 没有 Guava Maps 方法做类似的事情。 David Limkys 的回答是正确的。实际上,如果你想从其他线程中使用它而不同步,你无论如何都需要一些ConcurrentMap。关于数据库线程,您可以做其他事情:要求工作线程创建快照并将其发送到数据库线程。但这肯定比使用 ConcurrentSkipListMap 更复杂,这肯定足够好了。
  • @biziclop - 目前没有使用同步,因为只有一个工作线程在循环中更新地图。

标签: java guava


【解决方案1】:

这取决于你想要什么:

如果你想要一个完全并发的地图(添加时无法读取等等)你应该使用 JSlain 在我之前说的。

如果您只需要地图的当前快照,并且您不关心地图是否会被修改,只要您使用的迭代器不会被更改。

然后使用ConcurrentSkipListMap

这将为每个迭代提供一个新的独立迭代器,因此即使实际地图发生更改,您也不会注意到它。

您将在下一次更新中看到它(在您的情况下为 5 秒。)

【讨论】:

  • 哇,不知道那个。听起来像他需要的。
  • 我相信这正是我想要的。当您假设我需要的只是当前快照时,您是对的。让我想知道他们是怎么做到的。每次修改都会创建一个副本?迫不及待想看看它的表现如何。
  • 我们可以检查实现,但我会假设迭代器的每个请求都会创建一个副本。它是性能和内存之间的权衡。但是有一个有 100 个键的地图,意味着有 100 个副本。(假设你只添加..)我会创建一个新的。
【解决方案2】:

来自TreeMapjavadoc:

请注意,此实现不同步。如果多个线程 访问地图 同时,并且至少有一个线程在结构上修改映射,它必须是 外部同步。 (结构修改是添加或删除一个 或更多映射;仅更改与现有键关联的值不是 结构修改。)这通常是通过在某些对象上同步来完成的 自然地封装了地图。如果不存在这样的对象,则应使用“包装”地图 Collections.synchronizedSortedMap 方法。这最好在创建时完成,以防止 意外不同步访问地图:

   SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));

【讨论】:

  • 使用 synchronizedSortedMap 是不够的:复制地图的整个代码块必须同步。
  • 我认为通过使用同步映射,您可以复制它,它会阻止其他线程尝试访问它。这样,您不必修改调用者,以便它使用同步块,这会使代码变臭。
  • 不。阅读 javadoc:当迭代其任何集合视图时,用户必须在返回的排序映射上手动同步。副本显然会遍历映射的条目,因此需要同步。
  • 确实......如果你使用同一张地图而不是使用副本,请按照 David Limkys 所说的那样使用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
相关资源
最近更新 更多