JDK8的ConcurrentHashMap的初始化源码及注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Initializes table, using the size recorded in sizeCtl.
* 使用sizeCtl中记录的大小初始化表。
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
//只要表为空,就一直循环
while ((tab = table) == null || tab.length == 0) {
//如果sizeCtl小于0,
if ((sc = sizeCtl) < 0)
//用了yield方法后,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
Thread.yield(); // lost initialization race; just spin 失去了初始化CPU竞争;只是自旋
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { //如果SIZECTL与sc相同,则把SIZECTL设置为-1,即当前线程获取到了初始化的工作。
try {
//再次确认 表为空
if ((tab = table) == null || tab.length == 0) {
//如果初始化size大于0,则n为sc,否则为16.
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
//创建n个Node数组
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//table指向空数组
table = tab = nt;
// 如果 n 为 16 的话,那么这里 sc = 12
// 其实就是 0.75 * n
sc = n - (n >>> 2);
}
} finally {
//sizeCtl重新定义新的值,用于扩容。
sizeCtl = sc;
}
break;
}
}
return tab;
}

由源码可知:初始化的代码比较简单,通过初始化数组,之后将sizeCtl赋值。
这里涉及到的知识点:

  1. Thread.yield():使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
  2. U.compareAndSwapInt(this, SIZECTL, sc, -1)和SIZECTL = U.objectFieldOffset(k.getDeclaredField(“sizeCtl”)):通过反射获取sizeCtl,再通过CAS设置其值。
  3. sc = n - (n >>> 2):其实就是 0.75 * n,扩容阈值。

这个方法并发通过对SIZECTL+CAS实现。