ConcurrentHashMap 1.7
Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦初始化就不能改变,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程 并发。
Java 7 中 ConcurrnetHashMap 的初始化。
1. 必要参数校验。
2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无参构造 默认值是 16.
3. 寻找并发级别 concurrencyLevel 之上最近的 2 的幂次方值,作为初始化容量大小, 默认是 16。
4. 记录 segmentShift 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时 计算位置时会用到。默认是 32 - sshift = 28.
5. 记录 segmentMask,默认是 ssize - 1 = 16 -1 = 15.
6. 初始化 segments[0],默认大小为 2,负载因子 0.75,扩容阀值是 2*0.75=1.5,插 入第二个值时才会进行扩容。
扩容 rehash
ConcurrentHashMap 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组 时,位置要么不变,要么变为 index+ oldSize,参数里的 node 会在扩容之后使用链表头 插法插入到指定位置。
ConcurrentHashMap 1.8
可以发现 Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 Segment 数组 + HashEntry 数组 + 链表,而是 Node 数组 + 链表 / 红黑树。当冲突链 表达到一定长度时,链表会转换成红黑树。
Java 8 中 ConcurrnetHashMap 的初始化。
/**
* 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 < 0 ,说明另外的线程执行CAS 成功,正在进行初始化。
if ((sc = sizeCtl) < 0)
// 让出 CPU 使用权
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
从源码中可以发现 ConcurrentHashMap 的初始化是通过自旋和 CAS 操作完成的。里面 需要注意的是变量 sizeCtl ,它的值决定着当前的初始化状态。
1. -1 说明正在初始化
2. -N 说明有N-1个线程正在进行扩容
3. 表示 table 初始化大小,如果 table 没有初始化
4. 表示 table 容量,如果 table 已经初始化。
总结
Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个 线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。
Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑 树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑 树,在冲突小于一定数量时又退回链表。