# 图解集合5：不正确地使用HashMap引发死循环及元素丢失

266人阅读 评论(0)

public class HashMapThread extends Thread
{
private static AtomicInteger ai = new AtomicInteger(0);
private static Map<Integer, Integer> map = new HashMap<Integer, Integer>(1);

public void run()
{
while (ai.get() < 100000)
{
map.put(ai.get(), ai.get());
ai.incrementAndGet();
}
}
}

public static void main(String[] args)
{
hmt0.start();
hmt1.start();
hmt2.start();
hmt3.start();
hmt4.start();
}

void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}

Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}

1、取当前table的2倍作为新table的大小

2、根据算出的新table的大小new出一个新的Entry数组来，名为newTable

3、轮询原table的每一个位置，将每个位置上连接的Entry，算出在新table上的位置，并以链表形式连接

4、原table上的所有Entry全部轮询完毕之后，意味着原table上面的所有Entry已经移到了新的table上，HashMap中的table指向newTable

HashMap的一次正常扩容就是这样的，这很好理解。

 1 void transfer(Entry[] newTable) {
2     Entry[] src = table;
3     int newCapacity = newTable.length;
4     for (int j = 0; j < src.length; j++) {
5         Entry<K,V> e = src[j];
6         if (e != null) {
7             src[j] = null;
8             do {
9                 Entry<K,V> next = e.next;
10                 int i = indexFor(e.hash, newCapacity);
11                 e.next = newTable[i];
12                 newTable[i] = e;
13                 e = next;
14             } while (e != null);
15         }
16     }
17 }

CPU切换到线程B运行，线程B将整个扩容过程全部执行完毕，于是就形成了：

1、e=next，即e=7

2、判断e不等于null，循环继续

3、next=e.next，即next=7的next，也就是3

4、放置7这个Entry

1、e=next，也就是说e=3

2、判断e不等于null，循环继续

3、next=e.next，即3的next，也就是null

4、放置3这个Entry

3移到table[3]上去了，3的next指向7，由于原先7的next指向3，这样就成了一个死循环。

3 5 7又会有怎样的结果

5 7 3这个数字可不巧，扩容前相邻两个Entry被分配到扩容后同样的table位置是很正常的。关键的是，即使这种异常场景发生的可能性再低，只要发生一次，那么你的系统就部分甚至全部不可用了----除了重启系统没有任何办法。所以，这种可能会发生的异常场景必须提前扼杀。

OK，不扯了，前面讲了5 7 3导致了死循环，现在看一下正常的顺序3 5 7，会发生什么问题。简单看一下，就不像上面讲得这么详细了：

1、使用Collections.synchronizedMap(Mao<K,V> m)方法把HashMap变成一个线程安全的Map

2、使用Hashtable、ConcurrentHashMap这两个线程安全的Map

个人资料
等级：
访问量： 5万+
积分： 869
排名： 5万+
文章存档
最新评论