ConcurrentHashMap的UML结构如下
数据结构如下
ConcurrentHashMap内部维持一个Segment数组,存储一组锁,每个锁里面含有一个HashEntry数组,存取数据时先经过哈希算法判断出属于哪个锁管理,拿到锁后就进入了数据区,HashEntry对象是一个链表节点,相当于每个锁管理一个HashMap对象,由于此时操作都有锁保护,故ConcurrentHashMap相当于管理了多个线程安全的HashMap对象
下面来看下ConcurrentHashMap的主要源码
ConcurrentHashMap初始化
ConcurrentHashMap有5个构造函数,但其中4个最终调用的是第一个参数最多的构造函数,最后一个跟无参构造函数相比就多了一步putAll方法初始化了一组数据
看下第一个构造函数源码
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
//如果加载因子小于0或者初始化容量小于0或者并发级别小于0 就抛出非法参数异常
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//如果锁的数量大于最大分段数 就将锁的数量设置为最大分段数
//MAX_SEGMENTS 为1<<16=65536,也就是最大并发数为65536
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//变量sshift用于记录ssize增加的次数
// //2的sshif次方等于ssize,例:ssize=16,sshift=4;ssize=32,sshif=5
int sshift = 0;
变量ssize用于记录小于锁的数量的最大偶数
int ssize = 1;
while (ssize < concurrencyLevel) {
//sshift增加1,
++sshift;
//ssize扩大为原来的2倍
ssize <<= 1;
}
//散列运算的位数
this.segmentShift = 32 - sshift;
//散列运算的掩码
this.segmentMask = ssize - 1;
//如果初始化容量大于最大容量 那么就设置初始化容量为最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//计算cap的大小,即Segment中HashEntry的数组长度,cap也一定为2的n次方.
//如果初始化容量大于最大容量 那么就设置初始化容量为最大容量
int c = initialCapacity / ssize;
/* 如果initialCapacity/ssize能整除
* 那么c*ssize =initialCapacity
* 如果initialCapacity/ssize不能整除有余数
* 那么就将分段数+1
* 获取最小分段数量 即每个分段锁可控制的元素数组最大长度
*/
if (c * ssize < initialCapacity)
++c;
// 默认 MIN_SEGMENT_TABLE_CAPACITY 是 2,这个值也是有讲究的,因为这样的话,对于具体的槽上,
// 插入一个元素不至于扩容,插入第二个的时候才会扩容
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// //创建segments数组并初始化第一个Segment,其余的Segment延迟初始化
Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
// 往数组写入 segment[0]
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
对象初始化后,开始插入对象
public V put(K key, V value) {
Segment<K,V> s;
//concurrentHashMap不允许value为空
if (value == null)
throw new NullPointerException();
//hash函数对key的hashCode重新散列,避免不合理的hashcode,保证散列均匀
int hash = hash(key);
//hash 是 32 位,无符号右移 segmentShift(28) 位,剩下高 4 位,
// 然后和 segmentMask(15) 做一次与操作,也就是说 j 是 hash 值的高 4 位,也就是槽的数组下标
// hash值是随机的 再跟15与操作 结果必然是0-15之间的一个数 保证了随机选取数组下标
int j = (hash >>> segmentShift) & segmentMask;
//
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}