跳转至

ConcurrentHashMap 底层:初始化流程

一、构造函数

public ConcurrentHashMap() {
    // 默认初始容量 16,加载因子 0.75
}

public ConcurrentHashMap(int initialCapacity) {
    // 找大于等于 initialCapacity 的最小 2 的幂
}

二、JDK 8 结构

ConcurrentHashMap
  ├── Node[] table        ← 数组,懒初始化
  ├── sizeCtl             ← 控制表初始化/扩容
  └── CounterCell[]       ← 分段计数

三、懒初始化

构造函数不初始化 table,第一次 put 才初始化:

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            Thread.yield();   // 别的线程在初始化,让出
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);   // 0.75n
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

四、sizeCtl 的含义

含义
-1 正在初始化
-(1 + 扩容线程数) 正在扩容
正数 下一次扩容阈值
0 默认

五、为什么懒初始化

和 HashMap 一样,省内存。很多 ConcurrentHashMap 创建了但不一定用,第一次 put 才分配数组。

六、Node 结构

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;          // volatile 保证可见性
    volatile Node<K,V> next;
}

链表节点,val 和 next 都是 volatile。

对比 HashMap

  • HashMap:构造函数可能初始化数组。
  • ConcurrentHashMap:懒初始化,CAS 控制并发初始化。