Hashmap jdk1.7死循环问题
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保存B和后面的链表。
e.next = arrayNew[hash];把A的next指向新的集合对应的链,第一次是null。
arrayNew [hash] = e;新集合的第一个元素指定为新加入的这个当前节点
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不是C而是A,把它作为对应hash位置的第一个节点,并且把它指向B,和第二轮的B->A 的链路就形成了环。
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本来就没有支持多线程环境。新版本使用为插入不会改变顺序不会出现死循环,但是多线程插入还是会数据不一致。
能耍的时候就一定要耍,不能耍的时候一定要学。
--天道酬勤,贵在坚持posted on 2023-09-11 21:27 zhangyukun 阅读(232) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
2019-09-11 用区块链技术做一个 不可被修改的 恋爱记录 app 我叫<<誓言>>
2019-09-11 java 调用区块链 发布和调用智能合约
2019-09-11 centos 以太坊多节点私链搭建
2018-09-11 linux 命令收集