面试经常被问到hashmap不安全,竟然是.....

HashMap的线程不安全体现在会造成死循环、数据丢失、数据覆盖这些问题。

其中死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而1.8中仍会有数据覆盖这样的问题。


1.7HashMap的线程不安全主要是发生在扩容函数中,即根源是在transfer函数

这段代码是HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插***将链表的顺序翻转,这也是形成死循环的关键点。

我们会发现转移的时候是逆序的。假如转移前链表顺序是1->2->3,那么转移后就会变成3->2->1。这时候就有点头绪了,死锁问题不就是因为1->2的同时2->1造成的吗?

简单点讲是一个线程扩容时1->2,2->null,然后这个线程发生阻塞,另一个线程执行完再回来执行发现1->22->1,这就是闭环,死循环,程序里你会发现卡住了,但不会报任何异常。


JDK1.8HashMap的线程不安全

阅读1.8的源码会发现找不到transfer函数,因为JDK1.8直接在resize函数中完成了数据迁移。JDK1.8在进行元素插入时使用的是尾插法



1第六行代码是判断是否出现hash碰撞,假设两个线程AB都在进行put操作,并且hash函数计算出的插入下标是相同的。

当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入。

然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

238行处有个++size,我们这样想,还是线程AB,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程A执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程B快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程A再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程AB都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。
自己在网上总结的,不是很全面,互相交流,原来链接找不到了,额....


全部评论
主要是hashmap根本没有加锁,也没有其他的同步机制,所以线程不安全。而不是由于数据覆盖导致的线程不安全。数据覆盖是结果,没加同步机制才是原因
3 回复
分享
发布于 2021-02-13 18:21
Bd
1 回复
分享
发布于 2021-02-13 13:34
小红书
校招火热招聘中
官网直投
点赞 回复
分享
发布于 2021-02-13 14:06
不是吧
点赞 回复
分享
发布于 2021-02-13 18:23
并发操作下的hashmap容易形成一个循环链表,因为它扩容的时候调用了resize方法。当获取一个不存在的key时,下标刚好在循环链表上,就会形成死循环
点赞 回复
分享
发布于 2021-02-18 17:17
你搜下,1.8貌似还是存在死循环问题
点赞 回复
分享
发布于 2021-02-19 06:14

相关推荐

点赞 评论 收藏
转发
6 36 评论
分享
牛客网
牛客企业服务