ConcurrentHashMap
里有 50+ 个子类, 一共 6k+ 行代码, 对于初学者直接阅读有一些难度. 本文详细介绍 ConcurrentHashMap
中的一些 关键要素(概念/代码), 掌握这些再看这个类的实现细节(代码), 就比较容易看懂.
推荐 JDK1.8 使用 liberica-1.8 版本, 这个版本的 JDK 所有源码都有, 方便阅读.
Oracle 版本的 JDK1.8 有一些类是没有源码的, 只有 .class
文件(比如 Unsafe
类), .class
反编译出来的代码看起来很难受.
1 ConcurrentHashMap 中的 Unsafe 类操作
这一小节是基础知识, 和 ConcurrentHashMap
设计没啥关系, JDK 中很多类都用到了 Unsafe
类.
Unsafe
类的主要功能:
- 获取类的字段(或数组的元素)的 偏移地址(offset).
- 通过对象的 偏移地址 获取/修改 对象的值(或引用).
- CAS 操作.
- 阻塞(park)/唤醒(unpark) 线程.
- 内存分配与回收.
ConcurrentHashMap
使用 Unsafe
的代码片段(有删减):
// Unsafe mechanics
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long ABASE;
private static final int ASHIFT;
static {
try {
// 获取 Unsafe 实例
U = sun.misc.Unsafe.getUnsafe();
// 获取 ConcurrentHashMap 的 Class 对象
Class<?> k = ConcurrentHashMap.class;
// ConcurrentHashMap 的 sizeCtl 成员/字段 的 地址偏移量(相对于ConcurrentHashMap对象的起始地址)
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
// 获取 Node[] 数组的 Class 对象
Class<?> ak = Node[].class;
// Node[] 数组的 第一个元素 的 地址偏移量(相对于数组对象的地址地址)
ABASE = U.arrayBaseOffset(ak);
// Node[] 数组一个元素的 地址偏移量scale
int scale = U.arrayIndexScale(ak);
// 偏移量scale 必须是 2的(n次)幂, 不是 2的幂则报错
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// Integer.numberOfLeadingZeros(scale): 计算 偏移量sacle 在转成二进制后,
// 从左往右看, 第一个 非0数 的左边一共有多少个 0.
// 假设 sacle=4, 那么 Integer.numberOfLeadingZeros(scale) 就等于 29.
// ASHIFT 表示: 元素的移位数
// 解释起来有点抽象, 看下面的 tabAt 方法的分析
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
对 以上定义的 偏移量 的使用:
// 获取 tab[] 在 下标i 处的对象; 相当于 具有 volatile 语义的 tab[i]
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
// ((long)i << ASHIFT) : 下标 i 处 Node 对象的地址偏移量(相对于tab对象的第一个元素的地址),
// ((long)i << ASHIFT) + ABASE : 下标 i 处 Node 对象的地址偏移量(相对于tab对象的起始地址),
// U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE) : 获取 tab[] 在 下标i 处的对象, 使用 volatile 语义.
// 比如:
// 假设 tab[] 中的一个 Node 元素所占内存偏移量为 4(scale=4), 那么,
// 易知 ASHIFT=2; 假设要获取下标为3(i=3)处的对象, 那么,
// 易知 ((long)i << ASHIFT)=12, 因此可通过 getObjectVolatile 获取 tab[i] 的对象, 即, tab起始地址偏移 12+ABASE 处的对象.
// 在这个例子中, i << ASHIFT 等价于 i*4.
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
//略
// this 表示当前的 ConcurrentHashMap(以下简称CHM) 对象,
// SIZECTL 是 CHM.sizeCtl 字段的 地址偏移量.
// 所以 U.compareAndSwapInt(this, SIZECTL, sc, -1) 意为:
// 如果 this.sizeCtl == sc, 就把 this.sizeCtl 赋值为 -1, 且这是一个原子操作.
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
//略
}
2 关键数据结构和变量
普通结点类型 Node
:
/* ---------------- Nodes -------------- */
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
// 成员方法略
}
扩容时 bin 的头部存放的结点类型 ForwardingNode
:
/* ---------------- Special Nodes -------------- */
/**
* A node inserted at head of bins during transfer operations.
*/
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
for (;;) {
int eh; K ek;
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
if (eh < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
return e.find(h, k);
}
if ((e = e.next) == null)
return null;
}
}
}
}
红黑树结构的结点类型 TreeNode
(如果数组某个下标位置(bin/桶)的结构为红黑树结构, 那么其桶内存储的节点则为TreeNode类型):
/**
* Nodes for use in TreeBins
*/
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
}
TreeBin
: TreeNode
的封装体, 用作放在数组下标上作为 根节点 使用, 但TreeBin
并不是真正的根节点, 根节点为其内部封装的 root
成员.
/* ---------------- TreeBins -------------- */
/**
* TreeNodes used at the heads of bins. TreeBins do not hold user
* keys or values, but instead point to list of TreeNodes and
* their root. They also maintain a parasitic read-write lock
* forcing writers (who hold bin lock) to wait for readers (who do
* not) to complete before tree restructuring operations.
*/
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock
static final int WAITER = 2; // set when waiting for write lock
static final int READER = 4; // increment value for setting read lock
// 方法略
}
CHM中主要的 成员变量:
/* ---------------- Fields -------------- */
/**
* The array of bins. Lazily initialized upon first insertion.
* Size is always a power of two. Accessed directly by iterators.
* bins 数组(存放bin, bin可理解为 "容器"/"桶", 其实就是 Node),
* 采用延迟初始化(在第一次插入时才初始化), 大小总是 2的幂(2的n次幂).
* 由迭代器直接访问.
*/
// CHM的 所有元素 都放在这个数组中
transient volatile Node<K,V>[] table;
/**
* The next table to use; non-null only while resizing.
*/
// 扩容时用于存放数据的变量, 扩容完成后会置为null
private transient volatile Node<K,V>[] nextTable;
/**
* 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.
*/
// 取值说明:
// 0: 未指定初始容量
// 正数(大于0): 说明初始化 map 时指定了容量, 假设指定的容量大小为x,
// 如果 x 是 2的幂, 则 sizeCtl=x,
// 如果 x 不是2的幂, 则 sizeCtl 取 2^n(满足 2^n > x 且 2^(n-1) < x).
// 比如, x=5或6或7或8时, sizeCtl=8; x=9, sizeCtl=16.
// -1: table 正在初始化
// -N(N大于1): N是int类型, 分为两部分, 高15位是指定容量标识, 低16位表示并行扩容线程数+1
private transient volatile int sizeCtl;
3 插入元素 put
// Maps the specified key to the specified value in this table.
// Neither the key nor the value can be null.
public V put(K key, V value) {
return putVal(key, value, false);
}
// Implementation for put and putIfAbsent
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// spread 意为把 高16位 伸展 到 低16位 做 XOR 运算.
// 其实就是一个 hash 扰动函数. 作用和 HashMap.hash 一样.
// 不过这里会强制最高位(符号位)为0.
int hash = spread(key.hashCode());
// 链表或树的结点个数
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
// f 意为 bin中的第一个结点(first); n 表示 table长度;
// i 表示 k-v 存储所在的数组下标; fh 表示 f 的 hash 值
Node<K,V> f; int n, i, fh;
// 默认构造不初始化 table, 这里判断 table 是否被初始化
if (tab == null || (n = tab.length) == 0)
// Initializes table.
tab = initTable();
// 如果 i 处的 bin 为空, 则通过 CAS操作 插入当前 k-v
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 当前结点正在扩容
else if ((fh = f.hash) == MOVED)
// 让当前线程调用helpTransfer也参与到扩容过程中来, 扩容完毕后tab指向新table
tab = helpTransfer(tab, f);
// i 处的 bin 不为空 且 没有在扩容
else {
V oldVal = null;
synchronized (f) {
// 双重检查i处结点未变化
if (tabAt(tab, i) == f) {
// bin 的第一个结点的 hash 值大于 0, 说明 bin 是链表结点类型
if (fh >= 0) {
// 链表结点个数 初始为 1
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// key 已存在
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent) // 可覆盖
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
// 把 k-v 封装为 新结点加入链表尾部
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// bin 后面 是 红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
// putTreeVal方法: Finds or adds a node. 如果添加成功返回null, 否则返回查找的结果
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// bin 中的 结点个数 大于等于 8, 则转成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 如果本次put是插入操作,那么size增加1, 并检测扩容
addCount(1L, binCount);
return null;
}
3.1 初始化 table
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// sizeCtl为负数, 说明已经有线程在给扩容(或初始化)
if ((sc = sizeCtl) < 0)
// 当前线程主动让出一下cpu
Thread.yield(); // lost initialization race; just spin
// 初始化时, CAS操作将 sizeCtl 置为 -1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
// 如果没有指定初始化容量大小, 则置为 16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 设置 扩容阈值 为新容量的 0.75 倍(3/4)
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
3.2 treeifyBin 方法把 链表 转为 红黑树
// 在给定索引处, 替换 bin 中的所有链表节点 为 红黑树结点.
// 表(table)太小时(小于64), 则 进行扩容 而不是 转红黑树.
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
// Tries to presize table to accommodate the given number of elements.
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
// head, tail
TreeNode<K,V> hd = null, tl = null;
// 遍历链表, 把链表结点的成员重新封装为 TreeNode, 并构造成双链表
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
// 赋值: 新构造的树结点 p 的前驱指向 tail
// 如果 tail 为空, 则说明 p 是 head
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p; // tail 指向新的尾部结点 p
}
// 构造红黑树
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
3.3 TreeBin构造函数
// 使用以 b 为首的初始节点集 创建 bin
TreeBin(TreeNode<K,V> b) {
// Node(int hash, K key, V val, Node<K,V> next)
super(TREEBIN, null, null, null);
this.first = b;
TreeNode<K,V> r = null;
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (r == null) {
x.parent = null;
// 根结点为黑色
x.red = false;
r = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = r;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// Tie-breaking utility for ordering insertions when equal hashCodes and non-comparable.
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 平衡红黑树
r = balanceInsertion(r, x);
break;
}
}
}
}
this.root = r;
assert checkInvariants(root);
}
3.4 addCount 方法
方法 addCount
可能会触发 table 扩容.
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 更新 Base counter value, 后面 sumCount()方法会根据 baseCount 计算 插入元素后的 table 容量
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 插入元素后的 table 容量 大于 当前扩容阈值 并且 小于最大扩容值时, 才扩容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 计算当前容量 n 的 resizeStamp
// resizeStamp 详解见下文
int rs = resizeStamp(n);
// sc<0 表示已经有线程在进行扩容工作
if (sc < 0) {
// (sc >>> RESIZE_STAMP_SHIFT) != rs 说明容量 n 变了
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
// TODO: 扩容
transfer(tab, nt);
}
// 第一个扩容的线程 会把 sc 置为 (rs << 16) + 2)
// (rs << 16) + 2) 表示: 高16位为 resizeStamp, 低16位为当前扩容线程数 + 1
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
3.5 resizeStamp 方法
resizeStamp
方法返回的结果 int
值的二进制解释:
高16位 为0, 低16位中: 最高位为 1, 低15位 存放当前容量 n
的 ‘stamp’ (n
的二进制最高位1前的0的个数).
ConcurrentHashMap
的容量必定是 2的幂, 所以不同的容量 n
前面 0 的个数必然不同, 所以 resizeStamp
的低 15 位 暗含了 原容量 n
.
// Returns the stamp bits for resizing a table of size n.
static final int resizeStamp(int n) {
// (1 << (RESIZE_STAMP_BITS - 1) 即: 00000000 00000000 10000000 00011100
// n 转成二进制后, 最高位 1 前 0 的个数
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
3.6 扩容时的 transfer
待更新…
4 get 方法
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
// table 不空 且 key 对应索引处的 bin 不空
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// bin 处即是要找的 mapping
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 在初始化或扩容
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 遍历 bin 的结点
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
引用
[1]. java.util.concurrent.ConcurrentHashMap(JDK1.8)
[2]. 关于jdk1.8中ConcurrentHashMap的方方面面