hashmap是在jdk1.7是数组+链表,通过hash计算出数组下标位置以后,如果同一个位置有多个元素,放在链表中,在多线程插入,并同时扩容的并发环境会出现死循环问题
头插入法
在维护链表元素的过程中,有一个head指针,指向第一个元素,没有尾部指针(未插入需要维护一个尾部指针,才能快递定位在哪里插入)。
举例子:A->B->C 这样一个结构使用头插入一个新元素D以后D->A->B->C
如果正好要扩容,那么久先扩容然后再插入,扩容也是头插入发
扩容以后变成
A->B->C 变成 C->B->A ,然后再插入 D ,D->C->B->A
单线程情况扩容过程
arrayOld ,arrayNew 两个集合,外层循环遍历数组,内层遍历链表
A->B->C
内循环过程
初始e = A;
next = e.next;备注B
e.next = arrayNew[hash];把A的next指向性的集合,第一次是null。
arrayNew [hash] = e;新集合的第一个元素指定为A
e=next;next是B然后开始第二次内循环
...
直到 C.next 是 null 退出循环,结果就是顺序倒过来
死循环产物过程
两个线程要插入元素,hash值相等,同一个下标,达到扩容条件,先触发扩容,然后触发插入,实际上插入我们都不关心了,扩容的过程多线程就有可能出现死循环。
假设还是 A->B->C
线程1,扩容进行到一半并且保存部分变量
初始e = A;
next = e.next;备注B
线程暂停......然后线程2执行玩扩容以后它才会继续,这时候 e=A,next=B 是放在本地方法栈中,我们先去看线程2
e.next = arrayNew[hash];把A的next指向性的集合,第一次是null。
arrayNew [hash] = e;新集合的第一个元素指定为A
e=B
这时候新集合是 A
第二轮循环
e=B;
next=e.next;线程2已经 把原链表改成C->B->A了,所以这时候是A,如果不出线程安全问题,它应该是C
e.next = arrayNew[hash];这时候新集合链表已经是B->A了
arrayNew [hash] = e;新集合的第一个元素指定为B
e=next,next这时候是A,不是C
第三轮循环
e=A;
next=A.next;是null;
e.next= arrayNew[hash];第二次循环的结果就是新集合链表已经是B->A了然后 a.next指向 B->的链表 ,结果就是A<->B,已经出问题了。
arrayNew [hash] = e;这时候把头节点指向A 这时候新集合的这个下标的链表 头节点指向A ,然后AB成环。
e=next;null退出循环。
线程2,直接扩容完成,容量倍增是原数据的2倍,但是当前生效的是和线程1 共用的。
线程2 执行完成以后数组里面的链表结果C->B->A
结论:hashmap本来就非线程安全的,在多线程环境本来就不应该用它,即便不出死循环问题,也会出别的问题。这并不是一个bug,而是hashmap本来就没有支持多线程环境。新版本使用为插入不会改变顺序不会出现死循环,但是多线程插入还是会数据不一致。
标签:hashmapjdk1.7,链表,hash,arrayNew,next,问题,插入,线程,死循环 From: https://www.cnblogs.com/cxygg/p/17694472.html