如果我们不使用 Collections.synchronizedMap()
,请说我有多线程环境。
我知道种族条件,重新尺寸问题等。
我的问题是可以有一个情况2线程Ta
和Tb
具有相同对象并尝试放入地图中。
如果没有2个条目,可以预防它。在2个不同线程同时运行的2个不同线程的2个put呼叫之间是否有一小部分的时间差异。
根据我的理解,对于Ta
和Tb
,都会在放置前检查,因此这里有重复键的情况。
考虑到我们已正确覆盖hashcode
和equals
。
HashMap
状态的javadoc:
请注意,此实现不同步。如果多个线程 同时访问哈希地图,至少一个线程 在结构上修改映射,它必须在外部同步。(A 结构修改是任何添加或删除一个或删除一个或 更多映射;只是更改与密钥相关的值 实例已经包含的不是结构性修改。)这是 通常是通过同步的某些物体自然而实现的 封装地图。如果没有这样的对象,则该地图应该是 使用collections.synchronizedmap方法"包装"。
因此,文档说您必须以某种方式同步访问,但不要说如果不这样做会发生什么。这意味着当您这样做时的行为是 undefined - 所有赌注都关闭了。
您可以自己查看HashMap
的源代码。put
的心脏是:
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
(编辑 - 这是Java 6中的实现。Java8的实现很大不同 - 加强了点)
如果两个线程同时尝试此结果,我们可以推测结果 - 但是很难推理。有时会导致两个带有相同键的条目,有时不会。这取决于时间。
TreeMap
的 put()
当然是完全不同的,当以这种方式滥用时,它的怪癖会有所不同。
任何这样的行为都是实施的怪癖,并且实现可能会在未来而没有警告的情况下改变,因为我们正在谈论未定义的行为。实施不承诺不会:
- 默默丢下条目
- 进入无限循环
-
NullPointerException
- 要求大量内存
- 破坏商店,以便丢失其他钥匙的条目
- 让先前删除的条目重新出现
- 创建包含堆内存垃圾的条目
- 等。
docs do 指出,从其他地方进行修改,而 Iterator
在对象上工作,将导致 Iterator
抛出 ConcurrentModificationException
-但这与同步是不同的关注点,并且可以如果您使用SynchronizedMap
总而言之,不要这样做。