Java 获取(对象)无限循环

Java 获取(对象)无限循环,java,multithreading,concurrency,hashmap,Java,Multithreading,Concurrency,Hashmap,一些答案提到,如果没有正确同步,HashMap中的get方法可能会陷入无限循环(例如,或)(通常底线是“不要在多线程环境中使用HashMap,使用ConcurrentHashMap”) 虽然我很容易理解为什么对HashMap.put(Object)方法的并发调用会导致无限循环,但我不太明白为什么get(Object)方法在尝试读取正在调整大小的HashMap时会卡住。我看了一下,它包含一个循环,但是退出条件e!=null迟早应该满足。它怎么能永远循环? 明确提到易受此问题影响的一段代码是: pu

一些答案提到,如果没有正确同步,HashMap中的get方法可能会陷入无限循环(例如,或)(通常底线是“不要在多线程环境中使用HashMap,使用ConcurrentHashMap”)

虽然我很容易理解为什么对HashMap.put(Object)方法的并发调用会导致无限循环,但我不太明白为什么get(Object)方法在尝试读取正在调整大小的HashMap时会卡住。我看了一下,它包含一个循环,但是退出条件
e!=null
迟早应该满足。它怎么能永远循环? 明确提到易受此问题影响的一段代码是:

public class MyCache {
    private Map<String,Object> map = new HashMap<String,Object>();

    public synchronized void put(String key, Object value){
        map.put(key,value);
    }

    public Object get(String key){
        // can cause in an infinite loop in some JDKs!!
        return map.get(key);
    }
}
公共类MyCache{
私有映射映射=新的HashMap();
公共同步的void put(字符串键、对象值){
map.put(键、值);
}
公共对象获取(字符串键){
//在某些JDK中可能导致无限循环!!
返回map.get(key);
}
}

有人能解释一下,一个线程将一个对象放入HashMap,而另一个线程从中读取对象时,线程如何以这样的方式交错,从而生成一个无限循环吗?这种情况是否与缓存一致性问题或CPU指令重新排序有关(因此该问题只能发生在多处理器机器上)?

鉴于我认为无限循环的唯一可能性是
e.next=e
get
方法中:

for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next)
如果只有一个线程在修改映射,我相信只有一个线程的无限循环是不可能的。在jdk 6(或5)之前的
get
旧实现中,这一点更为明显:

即使如此,这种情况似乎仍然不太可能,除非有很多碰撞


附言:我希望被证明是错的

您的链接用于Java6中的HashMap。它是用Java8重写的。在此重写之前,如果有两个写入线程,则可以在
get(Object)
上执行无限循环。我不知道
get
上的无限循环可以通过一个编写器实现

具体来说,当同时有两个调用
resize(int)
时,会发生无限循环,这两个调用:

如果两个线程正在处理同一个节点
e
,则第一个线程正常执行,但第二个线程设置
e.next=e
,因为第一个线程已将
newTable[i]
设置为
e
。节点
e
现在指向自身,当调用
get(Object)
时,它进入一个无限循环

在Java8中,resize保持节点顺序,因此循环不能以这种方式发生。但是,您可能会丢失数据


当维护访问顺序时,当有多个读卡器而没有写卡器时,
LinkedHashMap
类的迭代器可能陷入无限循环。对于多个读卡器和访问顺序,每次读取都会从节点的双链接列表中删除并插入被访问的节点。多个读卡器可能导致同一节点多次重新插入列表,从而导致循环。同样,该类已经为Java 8重写,我不知道这个问题是否仍然存在。

情况:

HashMap的默认容量为16,负载因子为0.75,这意味着当第12个键值对进入映射时(16*0.75=12),HashMap的容量将加倍

当两个线程同时尝试访问HashMap时,您可能会遇到无限循环。线程1和线程2尝试放置第12个键值对

线程1获得执行机会:

  • 线程1尝试放置第12个键值对
  • 线程1发现达到了阈值限制,并创建了容量增加的新存储桶。所以地图的容量从16增加到32
  • 线程1现在将所有现有的键值对传输到新的存储桶
  • 线程1指向第一个键值对和下一个(第二个)键值对以启动传输过程
  • 线程1在指向键值对之后,在开始传输进程之前,松开控件,线程2有机会执行

    线程2获得执行机会:

  • 线程2尝试放置第12个键值对
  • 线程2发现达到了阈值限制,并创建了容量增加的新存储桶。所以地图的容量从16增加到32
  • 线程2现在将所有现有的键值对传输到新的存储桶
  • 线程2指向第一个键值对和下一个(第二个)键值对以开始传输过程
  • 在将键值对从旧存储桶传输到新存储桶时,键值对将在新存储桶中反转,因为hashmap将在开始时添加键值对,而不是在结束时添加键值对。Hashmap在开始时添加新的键值对,以避免每次遍历链表并保持恒定的性能
  • 线程2将把所有键值对从旧存储桶转移到新存储桶,线程1将获得执行的机会
  • 线程1获得执行机会:

  • 离开控件之前的线程1指向旧bucket的第一个元素和下一个元素
  • 现在,当线程1开始将键值对从旧存储桶放到新存储桶时。它成功地将(90,val)和(1,val)放入新的存储桶中
  • 当它尝试将(1,val)的下一个元素(90,val)添加到新的Bucket中时,它将以无限循环结束
  • 解决方案:

    要解决此问题,请使用
    Collections.synchronizedMap
    ConcurrentHashMap

    ConcurrentHashMap是线程安全的,即代码一次可以由单个线程访问

    可以使用
    集合来同步HashMap。synchronizedMap(HashMap)
    方法。通过使用这个方法,我们得到一个HashMap对象wh
     do {
         Entry<K,V> next = e.next;
         int i = indexFor(e.hash, newCapacity);
         e.next = newTable[i]; //here e.next could point on e if the table is modified by another thread
         newTable[i] = e;
         e = next;
     } while (e != null);
    
    public Object get(Object key) {
            Object k = maskNull(key);
            int hash = hash(k);
            int i = indexFor(hash, table.length);
            Entry e = table[i]; 
            while (true) {
                if (e == null)
                    return e;
                if (e.hash == hash && eq(k, e.key)) 
                    return e.value;
                e = e.next;
            }
        }
    
     void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
             while(null != e) {
                 Entry<K,V> next = e.next;
                 if (rehash) {
                     e.hash = null == e.key ? 0 : hash(e.key);
                 }
                 int i = indexFor(e.hash, newCapacity);
                 e.next = newTable[i];
                 newTable[i] = e;
                 e = next;
             }
         }
     }
    
                 e.next = newTable[i];
                 newTable[i] = e;