扩展HashMap 并仅同步puts
我最近在代码库上遇到了一个扩展HashMap并同步put方法的类。
除了使用ConcurrentHashMap效率低之外,扩展HashMap和仅同步put(K,V)可能会出现什么样的问题?
假设我们不关心get(K)是否返回最新值(例如,我们可以使用线程覆盖彼此,并且我们不关心如果使用地图的值可能出现的竞争条件作为锁自己)。
例:
public class MyMap extends HashMap { //... public synchronized void put(K key, V value) { //... } //... }
据我所知,HashMap使用put方法重新resize,并且因为put在map实例级别同步,所以在(可能)不会遇到并发重新resize时遇到的问题。
即使有上面可疑的假设,我的直觉也告诉我,可能会出现更多问题。 或者我只是偏执狂?
更新:谢谢大家,这很有趣,也很有启发性。 如果我遇到这个特定class级的作者的原作,我现在可以详细解释他的愚蠢。 🙂
总结:putAll仍然可以搞砸数据结构,最终陷入可怕的无限循环/数据争用状态。 get依赖于hashmap的底层内部数据结构,这些结构可能正在被同时修改,导致get进程表现得很奇怪。 这只是个坏主意。 至少,作者可能已经使用了Collections.synchronizedMap(Map)。
注意:截至本文撰写时给出的所有三个答案实际上都是正确的,但我选择了一个关于get()作为正确答案的答案,因为对我来说这是最不明显的答案。
因为get()
可以读取不断变化的数据结构,所以一切都很糟糕。
我已经看到get()被困在死循环中,所以它不仅仅是理论上的可能性,还会发生坏事。
我希望你也在putAll
同步并remove
。 putAll
特别是因为多个线程可以尝试调整HashMap的大小。 这些方法也将更新size
和modCount
,如果在同步之外完成,可能会导致更新丢失。
正如我在评论中提到的,可能出现的另一个问题是putAll(Map)
方法似乎没有同步。 由于putAll
也可以修改Map的结构,因此从一个线程调用它不同步而另一个线程使用相同的Map是不安全的。
然而,在更高层次上,了解更多关于为什么put(key, value)
synchronized
的原因会很有趣。 即使你现在已经防止了对地图结构的不同步修改,但是如果多个线程在没有同步的情况下访问地图仍然不是一个好主意。 实际上,如果线程A试图迭代HashMap的内容,并且线程B调用synchronized put(key, value)
,则线程A中的迭代器仍将快速失败并抛出ConcurrentModificationException
而不是做一些非确定性的事情。
即使您要同步putAll(Map)
调用,迭代地图内容的其他线程仍会看到exception。 如果需要在多个线程中使用Map,并且至少有一个线程需要修改Map,则所有调用都需要同步,周期。