Java hashmap在多线程环境中可以有重复的键吗

Java hashmap在多线程环境中可以有重复的键吗,java,hash,collections,hashmap,Java,Hash,Collections,Hashmap,如果我们不使用Collections.synchronizedMap(),假设我有一个多线程环境 我知道比赛情况、重新调整尺寸等问题 我的问题是,是否有一个案例2线程Ta和Tb具有相同的对象并试图放入地图中 如果不是如何防止,是否可能有2个条目。同时运行的两个不同线程的两个put调用之间是否存在时间差的一小部分 根据我的理解,因为Ta和Tb都将在放置前进行检查,因此此处可能存在重复钥匙的情况 考虑到我们已经正确地覆盖了hashcode和equals。的Javadoc forHashMap声明:

如果我们不使用Collections.synchronizedMap(),假设我有一个多线程环境

我知道比赛情况、重新调整尺寸等问题

我的问题是,是否有一个案例2线程
Ta
Tb
具有相同的对象并试图放入地图中

如果不是如何防止,是否可能有2个条目。同时运行的两个不同线程的两个put调用之间是否存在时间差的一小部分

根据我的理解,因为
Ta
Tb
都将在放置前进行检查,因此此处可能存在重复钥匙的情况


考虑到我们已经正确地覆盖了
hashcode
equals

的Javadoc for
HashMap
声明:

请注意,此实现不同步。如果有多个线程 同时访问哈希映射,并至少访问其中一个线程 如果从结构上修改贴图,则必须从外部对其进行同步。(一) 结构修改是添加或删除一个或多个结构的任何操作 更多映射;只需更改与 实例已包含的不是结构修改。)这是 通常通过在某些对象上进行同步来完成 封装地图。如果不存在这样的对象,则应删除地图 使用Collections.synchronizedMap方法“包装”

因此,文档说您必须以某种方式同步访问,但没有说明如果不同步会发生什么。这意味着当你这样做的时候,行为是未定义的——所有的赌注都是无效的

你可以看看你自己。
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;
for(条目e=table[i];e!=null;e=e.next){
对象k;
如果(e.hash==hash&((k=e.key)==key | | key.equals(k))){
V oldValue=e.value;
e、 价值=价值;
e、 记录存取(本);
返回旧值;
}
}
modCount++;
加法器(散列、键、值、i);
返回null;
(编辑-这是Java 6中的实现。Java 8的实现有很大的不同——这加强了这一点)

如果两个线程同时尝试这样做,我们可以推测结果——但这很难推理。有时它会导致两个条目具有相同的密钥,有时则不会。这取决于时间

TreeMap
put()
当然是完全不同的,以这种方式滥用它的怪癖也会有所不同

任何这样的行为都是实现的一个怪癖,将来实现可能会在没有警告的情况下更改,因为我们讨论的是未定义的行为。实施不会向您承诺:

  • 静默删除条目
  • 进入无限循环
  • NullPointerException
  • 声称拥有大量内存
  • 损坏存储区,使具有其他密钥的条目丢失
  • 使以前删除的条目重新出现
  • 从堆内存创建包含垃圾的条目
  • 等等
文档确实声明,当
迭代器对对象进行操作时,来自其他地方的修改将导致
迭代器抛出
ConcurrentModificationException
——但这与同步不同,如果使用
SynchronizedMap


总之,不要这样做。

Javadoc for
HashMap
声明:

请注意,此实现不同步。如果有多个线程 同时访问哈希映射,并至少访问其中一个线程 如果从结构上修改贴图,则必须从外部对其进行同步。(一) 结构修改是添加或删除一个或多个结构的任何操作 更多映射;只需更改与 实例已包含的不是结构修改。)这是 通常通过在某些对象上进行同步来完成 封装地图。如果不存在这样的对象,则应删除地图 使用Collections.synchronizedMap方法“包装”

因此,文档说您必须以某种方式同步访问,但没有说明如果不同步会发生什么。这意味着当你这样做的时候,行为是未定义的——所有的赌注都是无效的

你可以看看你自己。
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;
for(条目e=table[i];e!=null;e=e.next){
对象k;
如果(e.hash==hash&((k=e.key)==key | | key.equals(k))){
V oldValue=e.value;
e、 价值=价值;
e、 记录存取(本);
返回旧值;
}
}
modCount++;
加法器(散列、键、值、i);
返回null;
(编辑-这是Java 6中的实现。Java 8的实现有很大的不同——这加强了这一点)

如果两个线程同时尝试这样做,我们可以推测结果——但这很难推理。有时它会导致两个条目具有相同的密钥,有时则不会。这取决于时间

TreeMap
put()
当然是完全不同的,以这种方式滥用它的怪癖也会有所不同

任何这样的行为都是实现的一个怪癖,将来实现可能会在没有警告的情况下更改,因为我们讨论的是未定义的行为。实施不会向您承诺:

  • 静默删除条目
  • 进入无限循环
  • NullPointerException
  • 声称拥有大量内存
  • 损坏存储区,使具有其他密钥的条目丢失
  • 使以前删除的条目重新出现
  • 从堆内存创建包含垃圾的条目
  • 等等