ConcurrentHashMap是线程安全的,通过分段锁的方式提高了并发度。分段是一开始就确定了,后期不能再进行扩容。
其中段Segment继承了重入锁ReentrantLock,有了锁的功能,同时含有类似HashMap的数组加链表的结构。
虽然Segment的个数是不能扩容的,但是单个Segment里面的数组是可以扩容的。
ConcurrentHashMap有3个参数:
initialCapacity:初始总容量:默认16
loadFactor:加载因子,默认0.75
concurrencyLevel:并发级别,默认16
然后我们需要知道的是:
segment的个数即ssize
取大于等于并发级别的最小的2的幂次。如concurrencyLevel=16,那么ssize=16,如concurrencyLevel=10,那么ssize=16。
单个segment的初始容量cap
c=initialCapacity/ssize,并且可能需要+1(即向上取整)。如15/7=2,那么c取3,如16/8=2,那么c取2。cap取的值就是大于等于c的最小2的幂次。但是cap最小值要求是2。
单个segment的阈值threshold
通过cap*loadFactor计算得出。
所以,默认情况下,segment的个数ssize=16,每个segment的初始容量是cap=2,单个segment的阈值threshold=1。
put过程
1、首先根据key计算出一个hash值,找到对应的Segment
2、调用Segment的lock方法,为后面的put操作加锁
3、根据key计算出hash值,找到Segment中数组中对应的index的链表,并将该数据放置到链表中
4、判断当前Segment包含元素的数量大于阈值,则Segment进行扩容
扩容过程
这个扩容过程是在Segment的锁的保护下进行扩容的,不需要关注并发问题。
这里的重点就是:
首先找到一个lastRun,lastRun之后的元素和lastRun是在一个桶中,所以后面的不需要进行变动。
然后对开始到lastRun部分的元素,重新计算下设置到newTable中,每次都是将当前元素作为newTable的首元素,之前老的链表作为该首元素的next部分。
get过程
1、通过key计算出对应的segment
2、再根据key计算出对应segment中数组的index
3、最终遍历上述index位置的链表,查找出对应的key的value