2.1 ConcurrentHashMap构造方法与数据结构分析
/**
-
Creates a new, empty map with the specified initial
-
capacity, load factor and concurrency level.
-
@param initialCapacity the initial capacity. The implementation
-
performs internal sizing to accommodate this many elements.
-
@param loadFactor the load factor threshold, used to control resizing.
-
Resizing may be performed when the average number of elements per
-
bin exceeds this threshold.
-
@param concurrencyLevel the estimated number of concurrently
-
updating threads. The implementation performs internal sizing
-
to try to accommodate this many threads.
-
@throws IllegalArgumentException if the initial capacity is
-
negative or the load factor or concurrencyLevel are
-
nonpositive.
*/
@SuppressWarnings(“unchecked”)
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) { //@1
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0; //@2 start
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1; //@2 end
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY; // 每个 segment 内部容里
while (cap < c)
cap <<= 1;
// create segments and segments[0]
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];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
代码@1 concurrencyLevel,含义,并发级别,并发度,在键不冲突的情况下,最多允许多少个线程同时访问数据不需要阻塞(理想情况下),我们应该知道,ConcurrentHashMap 的基本实现原理就是引入Segment 数据结构,将锁的粒度细化到Segment, 也就是说,如果多个线程,同时操作多个 key,如果这些 key,分布在不同的 Segment, 那这些线程的操作互不影响,当然不需要加锁,提高性能。所以 concurrencyLevel,就是要求告诉 ConcurrentHashMap, 我需要这么过个线程同时访问你而不产生锁冲突。
代码@2,ssize,该变量的值等于ConcurrentHashMap 中 segment 的长度,也就是 Segment[] 的长度。该值取决于concurrencyLevel, 其实就是小于concurrencyLevel 的最大的2的幂,比如concurrencyLevel= 16,那 ssize=16,如果 concurrencyLevel = 12, ssize=8,因为ssize的长度为2的幂。
变量shift的值,看出来了没,其实就是 ssize 2 ^ shift,其实就是表示ssize需要的二进制位。
segmentMask、segmentShift ,这两个属性在该表达式中使用:(h >>> segmentShift) & segmentMask),很明显,就是用来算Segment[]数组中的下标来的。意图segmentShift = 32 - sshift,也就是利用hash的高位与代表(ssize-1)来定位下标。// 如果默认,初始容量16,那么ssize=16, sshift=4 定位端 hash 无符号向右移多少28位,(总共32位),那就是使原本32-29位参与运算(高位)
变量cap,就是每个Segment中HashEntity[]的长度,大于【初始容量/segment长度】的最小2的幂。
分析到这里,ConcurrentHashMap就构建成功了,我们先重点关注一下 Segment 的数据结构。
Segment 段的内部数据结构如下:
-
类的声明:static final class Segment<K,V> extends ReentrantLock implements Serializable
-
数据结构:
transient volatile HashEntry<K,V>[] table; // 内部键值对
transient int count; // 元素数量
transient int modCount; // 结构发生变化的次数
transient int threshold; // 扩容时的阔值
final float loadFactor; // 扩容因子,主要影响threshold,影响什么时候扩容
对上述结构,是否似曾相识,对了,就是它,HashMap;每个 Segment 其实就是一个 HashMap; 还有一个很关键点:Segment继承自 ReentrantLock, 也就是 Segment 本身就是一把锁。
2.2 public V put(K key, V value) 源码分析
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException(); // @1
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask; //@2
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck in ensureSegment
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment @3
s = ensureSegment(j); //@4
return s.put(key, hash, value, false); //@5
}
代码@1,表明 ConcurrentHashMap不支持value为空的键值对。
代码@2,计算该key对应的Segment的位置(数组下标),并发包中获取数组元素的方式,采用的是UNSAFE直接操作内存的方式,而不是典型的 Segment[] a = new Segment[16], 第j个元素的值为 a[j]。如果需要详细了解UNSAFE操作数组元素的原理,请查看 另一篇博客(AtomicIntegerArray 源码分析)
比如一个Integer[]中,每个int是32位,占4个字节,那数组中第3个位置的开始字节是多少呢?=(3-1) << 2,也就是说SHIFT的值为元素中长度的幂。怎么获取每个元素在数组中长度(字节为单位)= UNSAFE.arrayIndexScale,
而 UNSAFE.arrayBaseOffset,返回的是,第一个数据元素相对于对象起始地址的便宜量,该部分的详解,请参考我的技术博客【http://blog.csdn.net/prestigeding/article/details/52980801】
代码@3,就是获取j下标的segment对象。相当于 if( (s == segments[j])== null )
代码@4,我们将目光移到 ensureSegment方法中:
/**