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