HashMap的线程不安全有三个方面:死循环,数据丢失,数据覆盖。其中死循环和数据丢失在Java8中已经得到解决。
多线程下扩容造成的死循环
我们都知道HashMap是通过链地址法解决哈希碰撞,即当哈希冲突时,会将相同哈希值的键值对通过单向链表的形式存放。
Java7中采用的是头插法,即下一个冲突的键值对会放在上一个键值对的前面。这就是形成死循环的关键点。
在分析这个问题之前我们先用模拟一下这个问题。创建多个线程不断进行put操作。即会出现如下死循环的情况,用jstack命令定位线程死循环的原因,如下:
从日志中可以看出问题出在transfer函数上(这个函数是在resize扩容方法中)。Java7中HashMap的transfer源码如下:
注意 e.next = newTable[i] 和newTable[i] = e 这两行代码,就会导致链表的顺序翻转。
如果是多线程环境下,假设有线程A,线程B都在进行put操作
线程A在执行到newTable[i] = e时被挂起,随后执行线程B,且线程B正常执行完成了resize操作
多线程下扩容造成的数据丢失
Java7中采用的头插法,除了引起死循环,还有数据丢失,同样的线程A,线程B进行put操作
线程A在执行到newTable[i] = e时被挂起,随后执行线程B,且线程B正常执行完成了resize操作
线程B执行完成后,现在15.next = null
继续向下执行
此时e = null 循环结束,5元素丢失
数据覆盖
首先我们看一下Java8中put操作的源码
1F
谢谢博主分享,多多更新哦!