HashMap为什么不是线程安全的

一、扩容过程

我们知道,HashMap在扩容的时候,是通过重新创建一个新的hash表,把原来旧数组中的Entry一个个迁移到新数组的,这个过程的实现方法如下

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;   
            //遍历第j条链
                do {
                    Entry<K,V> next = e.next;

                    // 计算在newTable中的位置,原来在同一条链上的元素可能被分配到不同的位置
                    int i = indexFor(e.hash, newCapacity);   
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

该方法中最核心的便是链的重新生成,代码如下

Entry<K,V> next = e.next;

 // 计算在newTable中的位置,原来在同一条链上的元素可能被分配到不同的位置
 int i = indexFor(e.hash, newCapacity);   
e.next = newTable[i];
newTable[i] = e;
e = next;

举个例子,现在有一条链,里面有四个元素,分别是

经过计算,它们中e1,e2,e4要迁移到newTable[2]中,初始状态为

e指向e1,next指向e2

1.迁移e1

由于一开始newTable[2]里没有元素,所以newTable[2]为null

e.next=newTable[2];将e1连接到了null

newTable[2]=e; 此时新数组中链头放了e1

e=next;指针后移,状态如下

2.迁移e2

e.next=newTable[2];将e2的连接到了e1

newTable[2]=e; 此时新数组中链头放了e2

e=next;指针继续后移,状态如下

3.迁移e3

e3会迁移到另一个桶里,原理跟迁移e1是一样的

4.迁移e4

同理e2

最后迁移完的链为 e4--->e2--->e1

newTable[2]桶里装着e4

小总结:其实就是对着原链表,利用一个桶位进行重链(把新进来的元素连接上链头元素,并占用这个链头

二、多线程下出现的问题

假设有两个线程,1和2

线程1执行到第一步,也就是这个状态,就被挂起了

然而线程2顺利的完成了该子链的迁移,如下

此时我们发现,线程1的e和next如上,还是指向在e1和e2,然而在内存中整条链的顺序都变了

在这时,线程1又继续工作,按照指令

迁移第1次:

e.next=newTable[2]; 此时的链头已经由线程2改为e4了,所以e1会连上e4

newTable[2]=e; 

e=next; 这里e1又指向e2

next=e.next;变为

迁移第2次:

将e2连接上链头e1,e2作为链头,变为

迁移第3次:

将e1连接上链头e2,e1作为链头,e指向next,next指向e的下一个,变为

我们会发现迁移来迁移去都无法跳出这个环形链表,所以会发生扩容错误

以上是我个人的理解,如果读者发现有误还请不吝提醒

 

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页