HashMap线程不安全的场景
下图为HashMap的部分注解,大体意思是:如果多个线程同时访问HashMap,并且至少有一个线程做了结构上的修改
,那么它必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅仅修改键值不是结构修改。)
put
多个线程同时向Node[]的同一个位置插入
时,会发生覆盖
,只有一个线程的操作会被保留。
如下图源码,假如有A、B两个线程,同时在执行put且数组下标都为1,两个线程同时运行到第一个红框位置,判断tab[1]为null,A线程先执行tab[1]=NodeA,B线程再执行tab[1]=NodeB,这样B线程的操作就会覆盖A线程的。
第二个红框位置也会导致类似的问题,只不过是发生在链表上
,而不是Node[]上
resize + get
如果多个线程同时触发扩容resize
,可能会形成环形链表
,然后在调用get()
获取一个不存在的元素
时会发生死循环,导致CPU100%,JDK8已经解决了此问题。
主要原因就是,JDK7在Hash冲突时采用头插法
,扩容时若旧链表的元素还会Hash到同一个新链表,那么新链表与旧链表的顺序是反的(从 A-> B变成了B -> A),在1.8后采用尾插法
就不会出现这种问题,同时1.8的链表长度如果大于8就会转变成红黑树。
环形链表的形成
Java HashMap的死循环
JDK8的优化
HashMap全面分析及JDK8对HashMap的相关优化
remove
感觉Remove想法有错误,但是找不到问题在哪,欢迎指正不要误导更多的人。手动笑哭
多个线程同时remove会导致本来删除的元素重新回到链表上
假如有链表 A -> B -> C -> D,线程1删除B,线程2删除C,同时执行到红框出,导致以下情况,B.next = C且 B.next = D,发生了数据不一致,若线程2先执行线程1再执行的话,线程2:A -> B -> D ,线程1:A -> C,结果为A -> C不是 A -> D。
线程1:A.next = B.next = C
线程2:B.next = C.next = D
假如有链表 A -> B ,线程1删除B,线程2删除A,两个线程同时执行到红框处,然后如下执行
线程2:tab[i] = A.next = B
线程1:A.next = B.next = null
结果为 tab[i] = B;也就是说本来应该被删除的B还存在
size
由于++size
、--size
不是原子性操作,所以也会导致数据不一致