ConcurrentHashMap扩容
目录
一、tryPreSize方法-初始化数组
二、tryPreSize方法-扩容标识戳
三、transfer方法-构建新数组
四、transfer方法-迁移数据
五、transfer方法-lastRun机制
六、helpTransfer方法-协助扩容
三种触发方式
达到了扩容的阈值
一、tryPreSize方法-初始化数组
// 扩容前操作,putAll,链表转红黑树 插入map的长度(putAll) private final void tryPresize(int size) { // 这个判断是给putAll留的,要计算当前数组的长度(初始化) // 如果size大于最大长度 / 2,直接将数组长度设置为最大值。 // tableSizeFor,将长度设置的2的n次幂 // c是初始化数组长度 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); // sc是给sizeCtl赋值 // -1:正在初始化数组,小于-1:正在扩容,0:代表还没初始化数组,大于0:可能初始化了(代表阈值),也可能没初始化(初始化的长度) int sc; while ((sc = sizeCtl) >= 0) { // 代表没有正在执行初始化,也没有正在执行扩容。、 // tab:数组,n:数组长度 Node<K,V>[] tab = table; int n; // 判断数组是不是还没初始化呢 if (tab == null || (n = tab.length) == 0) { // 初始化数组,和initTable一样的东西 // 在sc和c之间选择最大值,作为数组的初始化长度 n = (sc > c) ? sc : c; // 要初始化,就直接把sizeCtl设置为-1,代表我要初始化数组 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { // DCL! if (table == tab) { // 创建数组 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; // 初始化数组赋值给成员变量 table = nt; // sc先设置成阈值 sc = n - (n >>> 2); } } finally { // 将sc赋值给sizeCtl sizeCtl = sc; } } } // 要么是c没有超过阈值,要么是超过最大值,啥事不做~~~ else if (c <= sc || n >= MAXIMUM_CAPACITY) break; // 省略部分代码。 } }
二、tryPreSize方法-扩容标识戳
// 扩容前操作 private final void tryPresize(int size) { while ((sc = sizeCtl) >= 0) { // 省略部分初始化代码 Node<K,V>[] tab = table; int n; if (tab == null || (n = tab.length) == 0) { // 扩容前操作! else if (tab == table) { // 计算扩容标识戳(基于老数组长度计算扩容标识戳,因为ConcurrentHashMap允许多线程迁移数据。) int rs = resizeStamp(n); // 这里是一个BUG,当前sc在while循环中,除了初始化没有额外赋值的前提下,这个sc < 0 永远进不来。 // 虽然是BUG,但是清楚sc < 0 代表正在扩容 if (sc < 0) { Node<K,V>[] nt; 31 ~ 16 15 ~ 0 // 这里是第二个BUG if ((sc >>> RESIZE_STAMP_SHIFT) != rs || // 判断协助扩容线程的标识戳是否一致 sc == rs << RESIZE_STAMP_SHIFT + 1 || // BUG之一,在判断扩容操作是否已经到了最后的检查阶段 sc == rs << RESIZE_STAMP_SHIFT + MAX_RESIZERS || // BUG之一,判断扩容线程是否已经达到最大值 (nt = nextTable) == null || // 新数组为null,说明也已经扩容完毕,扩容完毕后,才会把nextTable置位null transferIndex <= 0) // transferIndex为线程领取任务的最大节点,如果为0,代表所有老数据迁移任务都没领干净了 break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } // 还没有执行扩容,当前线程可能是第一个进来执行扩容的线程 // 基于CAS的方式,将sizeCtl从原值改为 扩容标识戳左移16位 // 10000000 00011010 00000000 00000010 一定是< -1的负数,可以代表当前ConcurrentHashMap正在扩容 // 为什么是低位+2,代表1个线程扩容。 低位为5,就代表4个线程正在并发扩容 // 扩容分为2部:创建新数组,迁移数据。 // 当最后一个线程迁移完毕数据后,对低位-1.最终结果低位还是1,需要对整个老数组再次检查,数据是否迁移干净 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_S