【问题标题】:Extending HashMap<K,V> and synchronizing only puts扩展 HashMap<K,V> 并仅同步 puts
【发布时间】:2012-09-14 18:26:41
【问题描述】:

我最近在我们的代码库中遇到了一个类,它扩展了 HashMap 并同步了 put 方法。

除了比使用 ConcurrentHashMap 效率低之外,扩展 HashMap 并仅同步 put(K,V) 可能会出现哪些问题?

假设我们不关心 get(K) 是否返回最新值(例如,我们可以很好地处理线程相互覆盖,并且我们不关心如果地图本身用作锁)。

例子:

public class MyMap<K,V> extends HashMap<K,V> {
  //...
   public synchronized void put(K key, V value) {
      //...
   }

  //...
}

据我所知,HashMap 使用 put 方法重新调整大小,并且由于 put 在 map 实例级别同步,因此在并发调整大小期间遇到的问题(可能)不会遇到。

即使有上述有问题的假设,我的直觉告诉我可能会出现更多问题。还是我只是偏执?

更新:谢谢大家,这很有趣,很有启发性。如果我遇到过这门课的原作者,我现在可以详细解释他的愚蠢。 :)

总结: putAll 仍然会严重破坏数据结构并最终陷入可怕的无限循环/数据竞争状态。 get 依赖于 hashmap 的底层内部数据结构,这些数据结构可能正在同时被修改,导致 get 过程行为异常。 这只是一个普遍的坏主意。至少,作者可以改用 Collections.synchronizedMap(Map)。

注意:撰写本文时给出的所有三个答案实际上都是正确的,但我选择了关于 get() 的一个作为正确答案,因为它对我来说是最不明显的一个。

【问题讨论】:

  • 但是哈希表是这样同步的。
  • 哈希表完全同步(放置和获取)。
  • 所以,同步获取会让它变得更慢
  • 是的,哈希表已经同步了 put 和 get ,所以它被锁定了。它很慢,但它是线程安全的。
  • 源代码中是否有任何迹象表明同步put(key, value) 方法的目标是什么?如果是为了避免两个线程同时在结构上修改map而不同步所有操作,那么又错过了一次写操作——putAll(Map)

标签: java concurrency synchronization


【解决方案1】:

我希望你也在 putAllremove 上同步。 putAll 特别是因为多个线程可以尝试调整 HashMap 的大小。这些方法也将更新sizemodCount,如果在同步之外完成可能会导致更新丢失。

【讨论】:

  • 当发生并发调整大小时,可能的数据结构损坏就更不用说了!
【解决方案2】:

由于get() 可以读取不断变化的数据结构,所有不好的事情都可能发生。

我已经看到 get() 陷入死循环,所以这不仅仅是理论上的可能性,坏事确实会发生。

【讨论】:

  • 如前所述,这是我的直觉告诉我的,但鉴于我的问题中所述的假设(不需要可见性保证,存储在地图上的值本身不会用作锁,我们可以值被覆盖),有哪些可能出错的地方?
  • @Hyangelo - 你误会了。除了(缺少)值的可见性保证之外,不保证 HashMap 内部的任何一致性。例如,您可以看到一个充满空值的内部表。或任何其他内部对象可能具有虚假的空值等...
  • @Hyangelo - 另请注意,值本身的可见性问题不仅仅是看到陈旧值的可能性,它还意味着值对象的内部状态可能是搞砸了(除非备用同步/易失性用于它们的内部状态)。
  • 你是对的,当一个遇到冲突的键完成获取时,它可能必须遍历某种列表,然后可能会发生如你所说的坏事。谢谢。
  • @Hyangelo 我已经看到 get() 陷入死循环,所以这不仅仅是理论上的可能性,确实会发生坏事。
【解决方案3】:

正如我在 cmets 中提到的,可能出现的另一个问题是 putAll(Map) 方法似乎没有同步。由于putAll也可以修改Map的结构,所以当另一个线程使用同一个Map时,从一个线程调用它是不同步的是不安全的。

不过,在更高的层次上,了解更多关于为什么put(key, value)synchronized为什么会很有趣。即使您现在已经防范了对映射结构的不同步修改,多个线程在不同步的情况下访问映射仍然不是一个好主意。事实上,如果线程 A 试图迭代 HashMap 的内容,而线程 B 调用 synchronized put(key, value),线程 A 中的迭代器仍然会快速失败并抛出 ConcurrentModificationException,而不是做一些不确定的事情。

即使您要同步putAll(Map) 调用,其他遍历映射内容的线程仍然会看到异常。如果 Map 需要在多个线程中使用,并且其中至少一个线程需要修改 Map,则所有调用都需要同步,期间。

【讨论】:

  • ConcurrentModificationException 上的好点虽然分辨率点与Collections.synchronizedMap 相同
猜你喜欢
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
相关资源
最近更新 更多