JDK1.7中关于多线程操作HashMap的成环以及丢失问题

JDK1.7中关于多线程操作HashMap的成环以及丢失问题

1. 预备知识

扩容产生的条件:

  • 如果HashMap初始化的时候,没有设置初始容量,那么初始容量默认为16,扩容阈值threshold为16*0.75f=12;
  • 如果设置了初始容量,那么首次扩容阈值threshold为初始容量。

HashMap的插入

  • JDK1.7的插入是头插法
	public HashMap(int initialCapacity, float loadFactor) {
	    if (initialCapacity < 0)
	        throw new IllegalArgumentException("Illegal initial capacity: " +
	                                           initialCapacity);
	    if (initialCapacity > MAXIMUM_CAPACITY)
	        initialCapacity = MAXIMUM_CAPACITY;
	    if (loadFactor <= 0 || Float.isNaN(loadFactor))
	        throw new IllegalArgumentException("Illegal load factor: " +
	                                           loadFactor);
	
	    this.loadFactor = loadFactor;
	    threshold = initialCapacity;// 👈在这里设置了扩容阈值,在构造函数中设置容量,阈值便会与与容量保持一致(第一次扩容之前)
	    init();
}

那么在添加Entry的时候,会进行判断

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {//size>=threshold,并且需要插入的位置不为空,可能出现扩容
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

addEntry->resize->transfer

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, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
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;
        }
    }
}

2. 关于成环

这里为了表示方便,我们设置初始容量为8,那么必须当size达到8的时候,再往里添加键值对才可能出现扩容现象。
我们使用的键值以及其在长度8和长度16的情况下对应的位置如下

Han1250--->len=8:1---- len=16:1
Han1251--->len=8:2---- len=16:2
Han1252--->len=8:3---- len=16:3
Han1253--->len=8:4---- len=16:4
Han1254--->len=8:5---- len=16:5
Han1255--->len=8:6---- len=16:6
ZH1252222--->len=8:6---- len=16:6
ZH125--->len=8:6---- len=16:6

那么在长度为8时,数据的排布情况为:
在这里插入图片描述
接下来我们默认以蓝色数组代表线程A创建的newTableA,绿色数组代表线程B创建的newTableB,新数组的长度都为原来的二倍,初始长度为8,旧数组命名为oldTble,扩容后长度为16。

2.1 线程A

在这里插入图片描述
在线程A中将前五个元素插入
在这里插入图片描述
接下来进入重点

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;//④
        }
    }
}

在这里插入图片描述
线程A运行到位置6的Entry时,e指向ZH125next指向ZH1252222,假设此时线程A失去了CPU,线程B得到了CPU。我们称之为剥夺
在这里插入图片描述

2.2 线程B

我们略过前几步。
在这里插入图片描述

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;//④

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 分析一下堆中的情况

OldTable作为底层数组,与HashMap对象生命周期相同,也是在堆中开辟的空间,无论多少个线程,都是操作的这一个对象,如果不加锁进行限制,则会产生数据的错乱。
在经过了上面的操作之后,堆中的数据情况如下:

线程A和B中的enext的指向如下图:

2.4 我们重新转入线程A

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为社么会产生这种情况呢?由于链表的插入为头插法,我们设一个链表上的两个Entry为E1E2,假设我们在线程A中,e和next分别是E1E2的引用,这时候线程B抢夺到了CPU,E1E2在新的Table数组中的位置依然相同,仍旧在同一链表中,在oldTableA中两个Entry的顺序为E1->E2,经过线程B的操作,顺序变成了E2->E1

这时候我们回到线程A中

e:E1
nex:E2

将e设置到newTableA的i处,newTableA[i]=e; e.next=newTableA[i];
接下来,e=next;,e指向了E2,while进入了下一轮循环。
进入新的循环,next=e.next;,此时e指向E2,E2.next==e.next==E1

在这里插入图片描述在这里插入图片描述
我们继续将e头插入新的链表,e=next;next=e.next;
在这里插入图片描述
在这里插入图片描述

此时我们发现,如果将e继续头插入新链表,ZH125.next==ZH1252222,而ZH1252222.next=ZH125
在这里插入图片描述

2.5 动图

在这里插入图片描述

3. 分析Entry丢失的情况

3.1 前提信息

在这里插入图片描述

3.2 线程B

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.3 线程A

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3.4 线程B

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.5 动图

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值