JDK1.7中HashMap线程不安全的原因

1. JDK1.7 HashMap 基本参数

数据结构: 数组 + 链表
默认数组长度: 16 , 扩容阈值0.75 ,扩容为当前长度的2 倍
数据插入采用头插法 (头插法是导致线程不安全的原因,可能会造成链表循环)

1.1 为什么JDK1.7 HashMap 采用头插法

个人理解: 在HashMap的数据结构中, 插入数据到链表头是最快的插入方式

2. 源码分析

Node 类结构
JDK1.7中HashMap线程不安全的原因_第1张图片

通过next 字段记录下一个节点完成 链表结构

HashMap 类结构
JDK1.7中HashMap线程不安全的原因_第2张图片

这里的 table 成员变量即为HashMap的数据结构 数组 + 链表 , 链表由Node类实现

2.1 说明:只介绍线程问题相关源码, 其余只作简单说明

resize () 方法

JDK1.7中HashMap线程不安全的原因_第3张图片

当添加数据时达到了扩容阈值,则会调用resize()方法, 从方法中可以看到新创建了一个数组, 并作为参数传入transfer()方法

transfer() 方法

JDK1.7中HashMap线程不安全的原因_第4张图片

transfer方法功能为将成员变量table中的值遍历,并重新计算在值新的数组中的位置 (局部变量 i 就是当前节点在新数组中的索引 ) ,通过头插法转移会导致链表反转
在单线程中并不会出现问题,在多线程中用红框圈住的代码则有可能造成链表循环(下面具体解释)

头插法

数组中存放的Node节点就是链表的头
在put数据时会通过对key的hash值进行计算, 算出该节点因该存放于数组的索引位置
假设put了一组K,V 称为NN , 通过计算K的hash值算出应当插入table[2]中,这时table[2]中存在元素
将table[2]中元素指向NN的next字段,再将NN放入table[2]中即可完成头插法

注意

当transfer()方法执行完成后链表可能会出现反转的情况如下图
JDK1.7中HashMap线程不安全的原因_第5张图片

在多线程中可能会出现的情况: 假设线程A,B 同时put都创建了新的数组,同时执行到transfer()中, 线程A,B 都执行完成next的赋值操作,就是transfer方法的第五条代码 Entry next = e.next;这时,线程B挂起,线程A顺利执行完毕transfer()后,链表反转,线程B继续执行
JDK1.7中HashMap线程不安全的原因_第6张图片
这时线程B继续执行
第一次执行循环没有问题
JDK1.7中HashMap线程不安全的原因_第7张图片
第二次执行循环没有问题
JDK1.7中HashMap线程不安全的原因_第8张图片
第三次执行循环出现了链表循环也就是线程安全问题
JDK1.7中HashMap线程不安全的原因_第9张图片
此时链表出现了循环的状态, 在get时将会进入死循环的状态,也就是HashMap1.7线程不安全的原因之一

总结

在多线程的情况下1.7HashMap扩容时调用的resize方法创建一个新的数组,再调用tranfer方法进行数据转移,当两个线程同时达到tranfer方法时,如线程B时间片用完(方法中next变量已经赋值成功), 线程A顺利执行完成数据转移因为是头插法,所以链表结构反转,这时候线程B在进行数据转移时,有可能会发生链表循环,所以是线程不安全的.

最后

如果有内容错误的地方请大家务必指出, 第一次使用CSDN写笔记,格式不好看,多包涵

你可能感兴趣的:(JDK1.7中HashMap线程不安全的原因)