部分内容转载自,后续我在其基础上做更详细的解释
put方法
public
说明:put函数底层调用了putVal进行数据的插入,对于putVal函数的流程大体如下。
① 判断存储的key、value是否为空,若为空,则抛出异常,否则,进入步骤
② 计算key的hash值,随后进入无限循环,该无限循环可以确保成功插入数据,若table表为空或者长度为0,则初始化table表,否则,进入步骤
③ 根据key的hash值取出table表中的结点元素,若取出的结点为空(该桶为空),则使用CAS将key、value、hash值生成的结点放入桶中。否则,进入步骤
④ 若该结点的的hash值为MOVED,则对该桶中的结点进行转移,否则,进入步骤
⑤ 对桶中的第一个结点(即table表中的结点)进行加锁,对该桶进行遍历,桶中的结点的hash值与key值与给定的hash值和key值相等,则根据标识选择是否进行更新操作(用给定的value值替换该结点的value值),若遍历完桶仍没有找到hash值与key值和指定的hash值与key值相等的结点,则直接新生一个结点并赋值为之前最后一个结点的下一个结点。进入步骤
⑥ 若binCount值达到红黑树转化的阈值,则将桶中的结构转化为红黑树存储,最后,增加binCount的值。
在putVal函数中会涉及到如下几个函数:initTable、tabAt、casTabAt、helpTransfer、putTreeVal、treeifyBin、addCount函数。下面对其中涉及到的函数进行分析。
重要成员变量
1、siseCtl:在多个方法中出现过这个变量,该变量主要是用来控制数组的初始化和扩容的,默认值为0,可以概括一下4种状态:
- a、sizeCtl=0:默认值;
- b、sizeCtl=-1:表示Map正在初始化中;
- c、sizeCtl=-N:表示正在有N-1个线程进行扩容操作;
- d、sizeCtl>0: 未初始化则表示初始化Map的大小,已初始化则表示下次进行扩容操作的阈值;
2、table:用于存储链表或红黑数的数组,初始值为null,在第一次进行put操作的时候进行初始化,默认值为16;
3、nextTable:在扩容时新生成的数组,其大小为当前table的2倍,用于存放table转移过来的值;
4、Node:该类存储数据的核心,以key-value形式来存储;
5、ForwardingNode:这是一个特殊Node节点,仅在进行扩容时用作占位符,表示当前位置已被移动或者为null,该node节点的hash值为-1;
final
其中 initTable函数源码如下
/**
说明:对于table的大小,会根据sizeCtl的值进行设置,如果没有设置szieCtl的值,那么默认生成的table大小为16,否则,会根据sizeCtl的大小设置table大小。
关于initTable函数,我在知乎上提了个问题
JDK1.8中ConcurrentHashMap的初始化竞争失败的线程为什么需要yield,有意义吗?www.zhihu.comtabAt函数源码如下
static
说明:此函数返回table数组中下标为i的结点,可以看到是通过Unsafe对象通过反射获取的,getObjectVolatile的第二项参数为下标为i的偏移地址。
你可能会有疑问为什么使用这个方法而不是数组下标直接获取?
jdk7中关于并发map的读不加锁,如何理解下面这句话?www.zhihu.com读取到null的问题是初始化HashEntry时发生的指令重排序导致的,也就是在HashEntry初始化完成之前便返回了它的引用。1.6的加lock来解决,乐观锁CAS(compare and swap)是处理器提供的原语,能保证禁止该指令的指令重排序,且能把写缓冲区的所有数据刷新到内存中。
casTabAt函数源码如下
static
说明:此函数用于比较table数组下标为i的结点是否为c,若为c,则用v交换操作。否则,不进行交换操作。
helpTransfer函数源码如下
final
说明:此函数用于在扩容时将table表中的结点转移到nextTable中。
putTreeVal函数源码如下
final
说明:此函数用于将指定的hash、key、value值添加到红黑树中,若已经添加了,则返回null,否则返回该结点。
treeifyBin函数源码如下
private
说明:此函数用于将桶中的数据结构转化为红黑树,其中,值得注意的是,当table的长度未达到阈值时,会进行一次扩容操作,该操作会使得触发treeifyBin操作的某个桶中的所有元素进行一次重新分配,这样可以避免某个桶中的结点数量太大。
addCount函数源码如下
private
说明:此函数主要完成binCount的值加1的操作。