JAVA8 ConcurrentHashMap的源码完全分析
一. 概述
在ConcurrentHashMap
内部实现中,一个有table列表,列表中的元素指向一个桶(bin
),该桶的元素头有以下三种:
- 普通链表节点:通常是桶中元素小于8个,就是一个单链表,头元素
hash > 0
。 - 转移节点(
MOVED
): 表明当前正长扩容中,当前的节点元素已经被转移到新table中,头元素hash = -1
。 - 树节点(
TREEBIN
): 表示当前的桶是一个红黑二叉树桶,头元素hash = -2
。 - 占位节点(
RESERVED
):一般用于当key对应的值缺失需要计算的场景,在计算出新值之前临时占坑位用的,计算出来之后就用普通Node节点替换掉,头元素hash = -3
。
桶列表Table默认初始大小为n=16,最大为n=2^31,负载阈值为0.75*n,当桶中普通链表的元素数量超过8个就会转成红黑二叉树,当桶中红黑树的元素减少到6个就会转成普通的单链表形式。在扩容的过程中,每个线程转移数据的索引数量步伐为Max(NCPU > 1 ? (n >>> 3) / NCPU : n, 16)
,最小值为16。
二. 源码分析
2.1 hash的散列计算方法
对于table大小为n的表格,其散列计算方法为((hash^(hash >>> 16))&0X7FFFFFFF) & n
,其中n为2的幂值(n = 2^x
),源码如下:
// http://www.easysb.cn/?p=325&preview=true
// Jekkay Hu
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
...
int h = spread(key.hashCode());
...
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
2.2 构造函数
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
其中sizeCtl
的用作表初始化和扩容控制,具体可以参考注释,下面代码也有解释。
/**
* Table initialization and resizing control. When negative, the
* table is being initialized or resized: -1 for initialization,
* else -(1 + the number of active resizing threads). Otherwise,
* when table is null, holds the initial table size to use upon
* creation, or 0 for default. After initialization, holds the
* next element count value upon which to resize the table.
*/
private transient volatile int sizeCtl;
2.3 有意思的函数
-
函数
tableSizeFor
,功能是向上取2的幂的函数,如下
/**
* Returns a power of two table size for the given desired capacity.
* See Hackers Delight, sec 3.2
*/
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
-
根据索引获取表对应桶的几个主要方法,使用
sun.misc.Unsafe
的方法:
// 功能: return tab[i]
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// 功能: if(tab[i] == c) tab[i] = v
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 功能: tab[i] = v
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
2.4 元素插入
元素的插入较为复杂,可以直接看源代码的中文注释。
// 插入数据,putIfAbsent表示只有缺失时才插入,否则强制更新
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 不允许空值
if (key == null || value == null) throw new NullPointerException();
// 计算hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
// 初始化表格
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// tabAt原子操作f= tab[i],找到表格中对应的桶,i为hash投影的索引
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
// casTabAt原子操作,主要是tab[i]= new Node(...),如果失败则表示