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[] 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[] nt = (Node[])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[] 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[] 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

你可能感兴趣的:(面向对象,java,网络,算法)