• ConcurrentHashMap源码解析 3.put() 方法


    ConcurrentHashMap—put() 方法

    put() => putVal()

    写数据大体流程

    写前操作

    • ConcurrentHashMap 不允许 key 或 value 为 null,会抛出异常
    • 写数据前,会先对 key 的 hash 值进行一次加工 spread()

    写数据流程

    整个写数据是一个 自旋 (死循环) 的操作。

    • 情况1:当前的 table 还没有被初始化。调用 initTable() 去尝试初始化 table,然后继续自旋
    • 情况2:当前 table 已经被初始化,调用寻址算法 (hash & (table.length - 1)) 得到的桶位上的元素为 null,尝试使用 CAS 操作构造的节点写入桶位。成功退出循环,失败继续自旋
    • 情况3:当前 table 已经被初始化了,但是当前桶位的节点是 FWD 节点,说明此时 table 正在发生扩容操作,此时当前线程就去参与扩容。扩容后继续自旋
    • 情况4:说明当前桶位的形成了链表或者红黑树,先使用 synchronized 加锁锁住当前桶位(锁对象就是当前桶位的头节点),然后判断如果当前桶位形成了链表,就遍历链表中的节点去找是否存在和待插入的节点的 key 完全一致的节点,如果有的话就进行 value 替换,没有就将节点插入到链表末尾。 如果当前桶位已经形成了红黑树,就在树中找是否存在一个节点的 key 和待插入节点完全一致的节点,存在就将 value 替换。不存在就插入到树中。

    写后操作

    1. 如果当前桶位是链表的话,会检查是否达到了树化标准然后去树化。
    2. 如果待插入节点跟桶位上的某一个节点冲突后进行替换了(无论是链表还是红黑树),直接将冲突节点的 value 返回,不执行③的逻辑 (因为这是一次替换操作,没有新增节点)
    3. 没有发生冲突,说明这是一次添加操作,需要调用 addCount() 方法,内部先对table中的节点进行计数,然后判断是否达到扩容标准执行扩容逻辑
       public V put(K key, V value) {
            // 调用了putVal方法
            return putVal(key, value, false);
        }
    			||
    		 	||
             	\/ 
         /*
          *  @param key 元素的key
          *  @param value 元素的value
          *  @param onlyIfAbsent 是否替换数据
          *  		false -> 当put数据时,遇到Map中有相同k-v的数据,将其替换
          *  		true  -> 遇到Map中有相同k-v的数据,不替换,不进行插入
          */
         final V putVal(K key, V value, boolean onlyIfAbsent) {
            /* 
             * 控制 k 和 v 不为null
             * 注意 这里跟HashMap不同,
             * HashMap允许key为null或者value为null,ConcurrentHashMap不允许key或者value为null。
             */
            if (key == null || value == null) throw new NullPointerException();
            // 通过spread()扰动函数方法重新计算hash值,让高位也能参与进寻址运算,使哈希值更加散列,减小哈希冲突
            int hash = spread(key.hashCode());
            /*
             * binCount表示当前k-v 封装成node后插入到指定桶位后,在桶位中的所属链表的下标位置
             * 0 当前桶位为null,node可以直接放
             * 2 表示当前桶位可能已经树化为了红黑树
             */
            int binCount = 0;
            /*
             * 自旋
             * tab 引用map对象的table
             */ 
            for (Node<K,V>[] tab = table;;) {
                /*
                 * f  表示桶位的头节点
                 * n  表示散列表数组的长度
                 * i  表示key通过寻址计算后得到的桶位下标
                 * fh 表示桶位头节点的哈希值
                 */
                Node<K,V> f; int n, i, fh;
                /*
                 * CASE1:表示table尚未初始化
                 * 进入initTable()方法初始化 尝试初始化table,最终返回初始化完成的table
                 */
                if (tab == null || (n = tab.length) == 0)
                    tab = initTable(); 
                /*
                 * CASE2:table已经初始化,且通过寻址后发现当前桶位头结点为null
                 * i 表示key使用路由寻址算法((table.length - 1) & hash)得到 key对应 table数组的下标位置,tabAt 获取指定桶位的头结点 f
                 * 进入casTabAt尝试使用CAS添加Node
                 */
                else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                    // 期望tab的第i个位置是NULL,是NULL的话就将创建的Node赋值上去
                    if (casTabAt(tab, i, null,
                                 new Node<K,V>(hash, key, value, null)))
                        // 设置成功,结束自旋;失败(有其他线程写成功了),走其他逻辑继续自旋
                        break;                  
                }
                /*
                 * CASE3:table已经初始化,且通过寻址后发现当前桶位头结点为FWD节点,表示当前map正处于扩容(迁移)过程中
                 * (MOVED == -1 表示当前节点是FWD节点)
                 */
                else if ((fh = f.hash) == MOVED)
                    // 看到FWD节点后,当前节点有义务帮助当前map对象完成迁移数据的工作
                    // 学完扩容后再来看
                    tab = helpTransfer(tab, f);
                /*
                 * CASE4:当前桶位(不为NULL)可能是链表 也可能是 红黑树代理类TreeBin 
                 */
                else {
                    // oldVal:当插入节点的key存在时,会将旧值赋值给oldVal,返回给put方法调用处
                    V oldVal = null;
                    // synchronized加锁给当前桶位的头节点(理论上是头结点),(只对当前桶位加锁,不锁整个Map)
                    synchronized (f) { 
                        /* 
                         * 为什么这里又要对比一下,看看当前桶位的头节点,是否为之前获取的头结点?(tabAt()就是获取i位置上的节点)
                         * 为了避免其他线程已经将该桶位的头结点修改掉了,导致当前线程加错锁,就会出现问题,如果这里对比失败的话,就不需要在执行了
                         * 如果条件成立,说明加锁的对象没有问题,就可以进来造了!
                         */
                        if (tabAt(tab, i) == f) {
                            // 条件成立,说明当前桶位是链表
                            if (fh >= 0) {
                                /*
                                 * 1.当前插入key与链表中的所有元素的key都不一致,当前的插入操作就是将节点追加到链表的末尾,binCount表示链表长度
                                 * 2.当前插入key与链表当中的某个元素的key一致时,当前插入操作就是替换了 binCount表示冲突位置(binCount-1)   
                                 */
                                binCount = 1;
                                // 遍历链表,判断是否有节点的key与当前要插入元素的key完全一致,存在的话替换value,不存在插入到链表末尾
                                for (Node<K,V> e = f;; ++binCount) {
                                    // 当前循环节点 key
                                    K ek;
                                    // 条件1:e.hash == hash 成立 表示循环的当前元素的hash值与插入节点的hash值一致,需要进一步判断
                                    // 条件2:((ek = e.key) == key ||(ek != null && key.equals(ek)))
                                    //        成立:说明循环的当前节点与插入节点的key一致,发生冲突了
                                    if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                         (ek != null && key.equals(ek)))) {
                                        // 将当前循环的元素的值 赋值给oldVal
                                        oldVal = e.val;
                                        // 因为从put方法传进来的onlyIfAbsent为false,!onlyIfAbsent 取反 为true
                                        if (!onlyIfAbsent) 
                                            e.val = value; // 值替换
                                        break;
                                    }
                                    // 当前元素 与 插入元素的key不一致时,会走下面程序
                                    // 1.更新循环处理节点为 当前节点的下一个节点
                                    // 2.判断下一个节点是否为null,如果是null,说明当前节点已经是队尾了,插入数据需要追加到队尾节点的后面
                                    Node<K,V> pred = e;
                                    // 插入到结尾
                                    if ((e = e.next) == null) {
                                        pred.next = new Node<K,V>(hash, key,
                                                                  value, null);
                                        break;
                                    }
                                }
                            }
                            // 当前桶位已经变为了红黑树
                            else if (f instanceof TreeBin) {
                                // p表示红黑树中如果与你插入节点的key有冲突节点的话,则putTreeVal方法 会返回冲突节点的引用
                                Node<K,V> p;           
                                // 强制设置binCount为2,因为 binCount <= 1 时有其他含义(在addCount()方法中)
                                binCount = 2;   
                                // p不为null,说明真的发生了冲突
                                // putTreeVal就当成是往红黑树中插入元素的方法 如果插入成功则返回null,反之 会将这个冲突的节点赋给p
                                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                               value)) != null) {
                                    // 将冲突节点的值赋值给oldVal
                                    oldVal = p.val;
                                    // 因为从put方法传进来的onlyIfAbsent为false,!onlyIfAbsent 取反 为true
                                    if (!onlyIfAbsent)
                                        p.val = value; // 覆盖原key的值
                                }
                            }
                        }
                    }
                    // 说明当前桶位不为null,可能是红黑树,也可能是链表
                    if (binCount != 0) {
                        // binCount >= 8,说明当前桶位一定是链表
                        if (binCount >= TREEIFY_THRESHOLD)
                            // 可能树化(还得看table长度)
                            treeifyBin(tab, i);
                        // 说明当前线程插入的数据key,与原有k-v发生冲突,需要将原数据v返回给调用者
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            /*
             * 退出自旋之后,调用addCount
             * 方法作用:
             * 1.统计当前table一共有多少节点
             * 2.判断是够达到扩容标准,触发扩容
             */
            addCount(1L, binCount);
            // 默认没有发生冲突,返回null
            return null;
        }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159

    initTable()

    initTable方法流程

    当前线程尝试去初始化table,此时有两种情况:

    • sizeCtl 的值为 -1:表示当前有其他线程正在初始化 table,当前线程先释放 CPU 执行权,然后再次尝试直到table被初始化完成退出循环
    • sizeCtl 的值大于等于 0:当前线程使用CAS的方式尝试去初始化 table。初始化 table 后 (只有一个线程可以真正的初始化table),将 sizeCtl 的值变为当前table长度的0.75,即扩容阈值
        private final Node<K,V>[] initTable() {   
            /*
             * tab 表示table的引用                          
             * sc 表示sizeCtl的临时值
             */
            Node<K,V>[] tab; int sc;
            /*
             * 带条件(当前table尚未初始化)的自旋,当table被初始化后就退出
             */
            while ((tab = table) == null || tab.length == 0) {   
                /*
                 * 先判断sizeCtl的值,
                 * 1.sizeCtl < 0
                 *    1.1 sizeCtl = -1 表示有线程正在创建table数组,当前线程需要自旋等待
                 *    1.2 其它表示当前table数组正在扩容,高16位表示扩容的标识戳,低16位表示(1 + nThread)个线程正在并发扩容
                 * 2.sizeCtl = 0
                 *    表示创建table数组时,使用DEFAULT_CAPACITY(16)大小
                 * 3.sizeCtl > 0
                 *    3.1 如果table未初始化,表示初始化大小
                 *    3.2 如果table已经初始化,表示下次扩容时的触发条件(阈值)  
                 */
                if ((sc = sizeCtl) < 0)
                    // sizeCtl的值大概率为-1,即当前有其他线程正在进行初始化操作(创建table数组),当前线程没有竞争到初始化table的锁
                    // yield表示释放CPU执行权
                    Thread.yield(); // lost initialization race; just spin
                
                 /*
                 * 有两种情况会走到这里
                 * 2.sizeCtl = 0
                 *    表示创建table数组时,使用DEFAULT_CAPACITY(16)大小
                 * 3.sizeCtl > 0 
                 *    3.1 如果table未初始化,表示初始化大小
                 *    3.2 如果table已经初始化,表示下次扩容时的触发条件(阈值)      
                 *  
                 *     通过调用UnSafe的CAS操作获取锁去修改sizeCtl的值,CAS设置sc为-1,也就是设置sizeCtl为-1
                 *     注意:这里的CAS方法的SIZECTL常量就是sizeCtl变量的内存偏移地址,即修改的就是sizeCtl变量的值
                 */
                else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        // 仍然判断table是否为null,防止多线程情况下多次初始化,丢失数据
                        if ((tab = table) == null || tab.length == 0) {
                            // sc大于0,创建table时使用sc为指定大小,否则使用默认值16初始化table
                            int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                            @SuppressWarnings("unchecked")
                            // 创建Node[]
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            // 最终赋值给map.table
                            table = tab = nt;
                            // sc变为0.75n 即下一次的扩容阈值
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        /*
                         * 此时有两种情况
                         * 1.当前线程是第一次初始化table的线程 此时的sc就是下一次的扩容阈值
                         * 2.当前线程不是第一次初始化table的线程 所以不会进入初始化table的逻辑,但sizeCtl的值已经被改为-1了,最后需要将它的值改回来
                         */
                        sizeCtl = sc;
                    }
                    break;
                } 
            }
            // 最后返回tab
            return tab;
        }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    addCount()

    addCount() 方法的执行逻辑

    • 计数,先调用类似 LongAdder 的 add 逻辑,尝试去写数据
      • 如果 cells 数组已经初始化了 或 尝试 CAS 写数据到 base 中失败了,就会继续判断 cells 数组的情况然后可能进入 fullAddCount() 逻辑 (就是LongAdder 中的 longAccumulate() 方法)
      • 如果写成功,就会求一次节点个数总和

    注意:如果有线程真的调用了fullAddCount(),那么直接return,既不求和也不参与table数组扩容的逻辑。

    • 判断是否需要扩容
      • 先计算出来一个扩容标识戳,然后自旋进行判断当前线程是否可以参与扩容的逻辑
      • 第一次进来 当前 sizeCtl 肯定是正数,当前线程则是触发扩容的第一个线程,CAS 将 sizeCtl 值修改为负数,进入扩容逻辑
      • 此时 sizeCtl 被修改为负数,此时当前的 map 正处于扩容中,判断当前线程是否可以协助扩容 (五个条件见源码解析),可以参与的话,使用 CAS 修改 sizeCtl 的值(高16位表示标识戳,低16位表示参与扩容的线程数),CAS 尝试将扩容线程 +1,成功的话进入扩容逻辑,失败继续自旋。
     	private final void addCount(long x, int check) {
         	/*
         	 * 这一部分代码就是LongAdder中的代码,先add()然后调用sum()去求和。
         	 * as 表示LongAdder中的cells数组
         	 * b 表示LongAdder中的base 
         	 * s 表示table中的节点个数
         	 */
            CounterCell[] as; long b, s;
         	/*
         	 * 进入if的两个条件(或的关系)
         	 * 条件1:true->表示cells已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据
             *        false->表示当前线程应该将数据累加到 base
             * 条件2:true->表示写base失败,与其他线程在base上发生了竞争,当前线程应该去尝试创建cells
             *        false->表示写base成功,数据累加到base中了,当前竞争不激烈,不需要创建cells
         	 */   
            if ((as = counterCells) != null ||
               !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
                /*
                 * a 表示当前线程寻址后命中的cell
                 * v 表示当前线程写cell时的期望值
                 * m 表示当前cells数组的长度
                 */
                CounterCell a; long v; int m;
                // true表示未竞争,false表示发生了竞争
                boolean uncontended = true;
                /*
                 * 这里有三种情况会进入fullAddCount()方法:
                 *   1.cells数组为null
                 *   2.cells数组不为null,但是当前线程寻址后命中的Cell为null
                 *   3.cells数组不为null,并且命中的Cell也不为null,但是尝试CAS向cell中写数据失败
                 */
                if (as == null || (m = as.length - 1) < 0 ||
                    (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                    !(uncontended =
                      U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                    // fullAddCount()方法 就是 Striped64#longAccumulate()方法 在之前的LongAdder文章中分析过
                    // 详细讲解参考 [https://blog.csdn.net/weixin_53407527/article/details/127702138]
                    fullAddCount(x, uncontended);
                    // 考虑到fullAddCount()事情比较累,直接让当前线程不参与扩容的逻辑了 直接返回了
                    return;
                }
                /*
                 * check:
             	 *  put 方法进来
             	 *    check >= 1 桶位链表长度
             	 *    check == 0 插入的桶位没有数据
             	 *    check == 2 已经树化了
             	 *  remove 方法进来
             	 *    check == -1
                 */
                if (check <= 1)
                    return;
                // 求和(就是LongAdder中的sum方法),是一个期望值(最终一致性)
                s = sumCount();
            }
            
    // --------------------------LongAdder Code End --------------------------    
    // ----------------------------扩容逻辑 Start------------------------------ 
    
            // check >=0 表示一定是一个put操作调用的addCount
            if (check >= 0) {
                /*
                 * tab 引用table
                 * nt  表示nextTable(扩容新建的table)
                 * n   表示table数组的长度
                 * sc  表示sizeCtl的临时值
                 */
                Node<K,V>[] tab, nt; int n, sc;
                /*
                 * 先判断sizeCtl的值,走到这里sizeCtl可能的情况:
                 * 1.sizeCtl < 0 
                 *    1.1 sizeCtl = -1 表示有线程正在创建table数组,当前线程需要自旋等待 (不可能)
                 *    1.2 其它表示当前table数组正在扩容,高16位表示扩容的标识戳,低16位表示(1 + nThread)个线程正在并发扩容 (可能)	
                 * 2.sizeCtl = 0
                 *    表示创建table数组时,使用DEFAULT_CAPACITY(16)大小 (不可能)
                 * 3.sizeCtl > 0
                 *    1.如果table未初始化,表示初始化大小 (不可能)
                 *    2.如果table已经初始化,表示下次扩容时的触发条件(阈值) (可能)
                 */
                
                /*
                 * 自旋
                 * 条件1:s >= (long)(sc = sizeCtl)
                 * 		   true  -> 1.当前sizeCtl是一个负数,表示正在扩容
                 *				 -> 2.当前sizeCtl是一个正数,表示扩容阈值 需要扩容
                 *		   false -> 表示当前的元素数量没有达到扩容阈值,无需扩容
                 * 条件2:(tab = table) != null 恒成立
                 * 条件3:(n = tab.length) < MAXIMUM_CAPACITY)
                 *        true->表示当前table长度小于最大值限制,可以进行扩容
                 */
                while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                       (n = tab.length) < MAXIMUM_CAPACITY) {
                    /*
                     * 扩容批次唯一表示戳
                     * 假设当前16 -> 32 计算出来的 rs = 1000 000 0001 1011
                     */
                    int rs = resizeStamp(n);
                    /*
                     * sc小于0 表示当前table正在扩容
                     * 当前线程CAS尝试应该协助table完成扩容
                     */
                    if (sc < 0) {
                        /*
                         * 当前线程是否可以进行扩容的条件
                         * 以下条件只要有1个为true,就会跳出循环,不会再进行扩容
                         *
                         * 条件1: 
                         *   (sc >>> RESIZE_STAMP_SHIFT) != rs 
                         *   true->表示当前线程获得的扩容唯一表示戳 非 本批次扩容 
                         *   false->表示当前线程获得的扩容唯一表示戳 是 本批次扩容
                         *  
                         * 条件2:
                         *   sc == rs + 1 (JDK1.8这里有BUG,后续JDK版本已经修改)
                         *   实际上想表达的是 sc == (rs << 16) + 1 使低16位的扩容线程数+1(原来的代码表示的是高16位)
                         *   更标准的代码为:sc == (rs <<< RESIZE_STAMP_SHIFT) + 1
                         *   true->表示扩容完毕,当前线程不需要参与(低16位(1 + nThread)=1 则说明nThread为0 已经没有扩容的线程了 扩容完毕)
                         *   false->表示扩容还在进行中,当前线程可以参与
                         *
                         * 条件3:
                         *   sc == rs + MAX_RESIZERS (JDK1.8这里有BUG,后续JDK版本已修改)
                         *   实际想表达的是 sc == (rs << 16) + MAX_RESIZERS 使低16位的扩容线程数+最大扩容线程数(原来的代码表示的是高16位)
                         *   更标准的代码为:sc == (rs <<< RESIZE_STAMP_SHIFT) + MAX_RESIZERS
                         *   true->表示当前参与的扩容线程数已经到了最大数(65535-1) 当前线程不需要参与
                         *   false->表示当前当前参与的扩容线程数未到达最大数,前线程可以参与
                         *   
                         * 条件4:
                         *   (nt = nextTable) == null
                         *   true->表示本次扩容结束(nextTable只有当在扩容时不为null,扩容结束就置为null)  
                         *   false->表示本次扩容未结束
                         * 
                         * 条件5:
                         *   transferIndex <= 0
                         *   true->当前扩容进度结束,任务已经被分配完了,不需要再扩容了
                         *   false->还在扩容中,还有任务可以分配,当前线程可以参与
                         */
                        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                            break;
                       /*
                        * 前置条件:当前table正在执行扩容中,当前线程有机会参与扩容
                        * 条件成立:说明当前线程成功参与到扩容任务中,并且将sc低16位值+1,表示多了一个线程参与扩容工作
                        * 条件失败:1.当前有很多线程都在此处尝试修改sizeCtl,有其它一个线程修改成功了,导致你的sc期望值与内存中的值不一致 修改失败
                        *          2.transfer 任务内部的线程也修改了sizeCtl
                        * 失败继续自旋
                        */
                        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                            // 协助扩容线程,持有nextTable参数
                            transfer(tab, nt);
                    }
                    // 1000 0000 0001 1011 0000 0000 0000 0000 + 2 => 1000 0000 0001 1011 0000 0000 0000 0010
                    // CAS修改sizeCtl的值,成功的话将sizeCtl变为负数
                    // 条件成立,说明当前线程是触发扩容的第一个线程,在transfer方法需要做一些扩容准备工作
                    // 否则,当前线程不是第一个触发的
                    else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                                 (rs << RESIZE_STAMP_SHIFT) + 2)) // 这里的2 就是 (1 + nThread)
                        // 触发扩容条件的线程,不持有nextTable
                        transfer(tab, null); 
                    // 求和
                    s = sumCount();
                }
            }
        }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 元素个数的存储方式类似于 LongAdder 类,存储在不同的段上,减少 CAS 竞争失败的次数
    • 计算元素个数时把这些段的值及 baseCount 相加算出总的元素个数
    • 正常情况下 sizeCtl 存储着扩容门槛,扩容门槛为容量的 0.75 倍
    • 扩容时 sizeCtl (sizeCtl < 0) 高位存储扩容戳 (resizeStamp),低位存储扩容线程数加1 (1+nThreads)
    • 其它线程添加元素后如果发现存在扩容,也会尝试加入的扩容行列中来



    参考

  • 相关阅读:
    【自动化基础】手把手教零基础小白搭建APP的UI自动化环境
    java计算机毕业设计springboot+vue学生宿舍管理系统 elementui
    资深java面试题及答案整理
    动态SQL语句怎么写
    Android基础第七天 | 字节跳动第四届青训营笔记
    Node学习十四 —— 使用node创建HTTP请求
    罗技摄像头左右翻转
    文献阅读(183)MAGMA
    被Gartner列入十大战略技术趋势的“行业云”,不再是个伪命题?
    chk文件怎么恢复?chk文件恢复软件哪个好?
  • 原文地址:https://blog.csdn.net/weixin_53407527/article/details/127747728