测试HashMap死循环,有个网友要求两个线程,10分钟内测试出HashMap死循环,下面是代码。
通过CountDownLatch来控制两个线程同时开启,增加形成死循环的概率。自己定义了MyObj作为key,重写hashCode方法,让所有的节点都落在HashMap同一个桶,形成单链表,重写equals方法,通过值来判断来个key是否相同
public class HashMapTest {
private static Map<MyObj, Integer> map = new HashMapTemp<>();
//让两个线程同时开始执行put操作
private CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) {
HashMapTest test = new HashMapTest();
test.testHashMap();
}
public void testHashMap() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10000; i+=2) {
System.out.println(String.format("T1:%d", i));
map.put(new MyObj(i), Integer.valueOf(i));
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i < 10000; i+=2) {
System.out.println(String.format("T2:%d", i));
map.put(new MyObj(i), Integer.valueOf(i));
}
}
});
thread1.start();
latch.countDown();
thread2.start();
latch.countDown();
}
class MyObj {
private Integer key;
MyObj(Integer key) {
this.key = key;
}
//按照value值来判断两个对象是否相同
@Override
public boolean equals(Object obj) {
return this.key.equals(((MyObj)obj).key);
}
//使所有元素都落到同一个桶
@Override
public int hashCode() {
return Integer.valueOf(5).hashCode();
}
}
}
HashMap的扩容是无法固定的,容量都会以两倍的方式增长,所以我改写了HashMap的addEntry方法,把里面的resize方法的参数从2倍改成了每次扩容增加1个容量,增加扩容的次数;
//resize扩容改成扩容1个元素,增加死循环概率
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(table.length + 1);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
另外,增加了打印当前节点的值以及下一个节点的值,如果出现类似“===循环节点===e.v:560, e.next.v:571===”, 说明已经有节点是“{e.v:571, e.next.v:560}”,把日志拉出来搜一下就能够找到。
@Override
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null) {return putForNullKey(value);}
int hash = hash(key);
int i = indexFor(hash, table.length);
List<String> exists = new ArrayList<>();
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//打印当前节点以及下一个节点
System.out.print(String.format("{e.v=%s, e.next.v=%s}, ", e.value, e.next == null ? "null" : e.next.value));
//判断循环字符串
String keyValue = String.format("e.v:%s, e.next.v:%s", e.next == null ? "null" : e.next.value, e.value);
//判断当前节点是否存在循环节点,只是获取了部分死循环情况
if (exists.contains(keyValue)) {
//打印死循环节点
System.out.println();
System.out.println(String.format("===循环节点===e.v:%s, e.next.v:%s===", e.value, e.next == null ? "null" : e.next.value));
} else {
exists.add(String.format("e.v:%s, e.next.v:%s", e.value, e.next == null ? "null" : e.next.value));
}
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;
}