我们从ConcurrentHashMap的带参的构造方法为入口进行源码的分析,首先看到它的构造方法。
cap为初始容量,由上可知,当初始容量大于等于最大容量的一半时则map的初始容量为最大的容量,否则通过tableSizeFor这个方法进行初始容量的计算,我们可以进入方法看一下初始容量是怎么计算的。
意思就是获取比传入的容量大的并且是2的次幂的数。容量设置为2的次幂也是为了日后做扩容的时候方便。 回到构造方法,最后一行代码是将sizeCtl参数赋值为cap,sizeCtl这个参数是值扩容的阈值,这里只是给它赋一个值,后面并非就是这个初始容量值。
通过构造方法可知,构造方法中只是计算了初始容量的值,并没有初始化map的容器对象。接着我么就去看一下ConcurrentHashMap的put方法。
put方法里面调用了putVal方法,我们来进入putVal方法详细的看一下ConcurrentHashMap是如何向容器中添加数据的。
从这个方法里面我们也可以得知, ConcurrentHashMap是通过一个table也就是一个Node<K,V>[]来存储数据的。
可以看到这个Node其实就是一个Map.Entry,相当于一个链表结构。
继续回到put方法中,方法中第一个if先是判断的存储数据的table容器是否为空,如果为空则调用initTable方法进行一个初始化的动作,现在知道构造方法中只是做了一个初始容量的计算,其实把容器的初始化动作放到了第一次调用put方法的时候进行了。进入initTable方法。
这里是采用了cas操作来保证有多个线程进行put操作时容器只会初始化一次。 初始化完成之后将sizeCtl设置成0.75n,表示下一次扩容的阈值。
查看put方法的第二个if,这里是通过hash和数组长度mod之后计算得到的位置上并没有数据,则创建一个Node。
put方法的第三个if,如果正在扩容则帮助扩容,扩容方法我们后面再看,先看最后一个if分支,这里是如果存在hash冲突的情况,则添加到当前的Node下面,具体的方法内容如下
注意这个synchronized同步锁,它锁住的是当前的这个Node链表,由此可以知道ConcurrentHashMap的锁粒度为一个Node<K,V>。查看同步代码块中的判读,如果当前Node的hash值是大于等于0的,说明当前的ConcurrentHashMap还是一个数组加链表的结构。如果是链表则遍历链表,如果有key值一样的则覆盖当前的值,否则新增一个在链表后面加一个数据。随着遍历这里的binCount的变量是一直在累加的。再看下面第二个if,如果当前的节点是一个红黑树结构了,则通过红黑树去添加元素。
注意最后一段红色框中的代码,这个会最终判断binCount的值的大小,如果该值大于等于8的时候则链表会转换为红黑树。我们进入转换的方法看一下。
先判断当前map的当前数组的长度是否是大于64,如果数组长度没有大于64则不做链表到红黑树的转换,只做数组的扩容。
这里先是判断是否初始化了容器对象,如果没有初始化则进行一个初始化,如果初始化过了则最终会调用一个transfer方法。transfer中是实现扩容的具体逻辑。