JDK1.7 头插法倒追:
在多线程环境下,非线程安全的hashmap可能会造成的问题
package littlehow.map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* HashMapInfiniteLoop hashmap死循环造成的cpu 100%
* 有时候,会出现很奇怪的现象,那就是程序运行一段时间会出现宕机,重启之后就没事儿了
* 具体什么原因也很难查找。
* 在高并发环境下,因为hashmap的线程不安全性,可能会引起上述现象
* 现在就看看,这种现象如何发生的
*
* @author littlehow
* @time 2016-07-11 10:16
*/
public class HashMapInfiniteLoop {
//初始长度为4的hashmap
private Map<Integer, String> map = new HashMap<Integer, String>(4);
//线程数量
final int count = 200;
final CountDownLatch countDownLatch = new CountDownLatch(count);
@Before
public void init() {
//因为hash计算结果类似于mod,所以我们可以事先准备冲突的hash对
//rehash的源码,可以看出当size==threshold也就是4的时候会进行resize,
//从而重新分布元素
/** if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.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;
}
}
*/
// 3 11 7 15 put后这三个都出现了hash冲突,所以结构为 3 -> 11 -> 7 -> 15 的链表
//当再put19时,又出现冲突, 这时候就需要扩容。
// 正常情况形成两个链表 11 -> 3 -> 19 和 15 -> 7
//但是当执行线程以执行到如 Entry<K,V> next = e.next;获取到的next=11时线程被挂起;
//线程二来执行成功,线程一继续,e=7,next=15,进行rehash,执行到
//newTable[i] = e = 7; e = next = 15,而线程二已经排好 15 -> 7,
//很明显线程一形成的链表为7 -> 15,合并结果就是 15 -> 7 -> 15形成了环形链表
//这时候get(23)就会一直查找下去,因为元素一直拥有next。从而导致 infinite loop
//resize后大小为8
map.put(3, "littlehow");
map.put(11, "color wolf");
map.put(7, "black dog");
map.put(15, "blue blood");
}
@Test
public void conflict() {
/**
* 想要出现Entry<K,V> next = e.next;之后挂起线程非常困难,在并发量超级大的情况下有可能会出现
* 所以没有出现死循环是最可能的现象,生产环境往往是不可预测的
* 在多线程环境下使用map这样结构的类有两点建议:
* 1.使用ConcurrentHashMap
* 2.如果确定map元素多少的情况,最好初始化map尽量大,避免rehash
*/
for (int i = 0; i < count; i ++) {
new Thread("thread-" + (i + 1)) {
@Override
public void run() {
map.put(19, "new person");
countDownLatch.countDown();
}
}.start();
}
try {
TimeUnit.SECONDS.sleep(1);
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@After
public void over() {
System.out.println("value=" + map.get(23) + " , size=" + map.size());
}
}
JDK1.8以及之后尾插法已解决