4

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

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

假设我们不关心 get(K) 是否返回最新值(例如,我们可以很好地处理线程相互覆盖,并且我们不关心使用 map 的值时可能出现的竞争条件作为锁本身)。

例子:

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() 的一个作为正确答案,因为它对我来说是最不明显的一个。

4

3 回答 3

5

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

于 2012-09-14T18:35:00.770 回答
3

由于get()可以读取不断变化的数据结构,因此可能会发生任何不好的事情。

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

于 2012-09-14T18:33:57.553 回答
2

正如我在评论中提到的,可能出现的另一个问题是该putAll(Map)方法似乎没有同步。由于putAll还可以修改 Map 的结构,因此在另一个线程使用同一个 Map 时,将其从一个线程中调用为非同步是不安全的。

At a higher-level though, it'd be interesting to understand more of the why around why put(key, value) was synchronized. Even if you've now guarded against unsynchronized modifications to the map's structure, it still does not seem like a good idea for multiple threads to be accessing the map without synchronization. In fact, if thread A is attempting to iterate over the HashMap's contents, and thread B calls synchronized put(key, value), the iterator in Thread A will still fail fast and throw a ConcurrentModificationException rather than do something non-deterministic.

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

于 2012-09-14T18:57:43.997 回答