ConcurrentHashMap源码分析

1 结构分析

 

 

  /**
     * 散列表数组最大限制
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 散列表默认值
     */
    private static final int DEFAULT_CAPACITY = 16;
/**
     * 并发级别,jdk1.7遗留下来的,1.8只有在初始化的时候用了一用。
     * 不代表并发级别。
     */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * 负载因子,JDK1.8中 ConcurrentHashMap 是固定值
     */
    private static final float LOAD_FACTOR = 0.75f;

    /**
     * 树化阈值,指定桶位 链表长度达到8的话,有可能发生树化操作。
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 红黑树转化为链表的阈值
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 联合TREEIFY_THRESHOLD控制桶位是否树化,只有当table数组长度达到64且 某个桶位 中的链表长度达到8,才会真正树化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 线程迁移数据最小步长,控制线程迁移任务最小区间一个值
     */
    private static final int MIN_TRANSFER_STRIDE = 16;
  /**
     * 散列表,长度一定是2次方数
     */
    transient volatile Node<K,V>[] table;

    /**
     * 扩容过程中,会将扩容中的新table 赋值给nextTable 保持引用,扩容结束之后,这里会被设置为Null
     */
    private transient volatile Node<K,V>[] nextTable;

    /**
     * LongAdder 中的 baseCount 未发生竞争时 或者 当前LongAdder处于加锁状态时,增量累到到baseCount中
     */
    private transient volatile long baseCount;

    /**
     * 一、sizeCtl < 0
     *  1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
     *  2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
     *
     * 二、sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
     *
     * 三、sizeCtl > 0
     *  1. 如果table未初始化,表示初始化大小
     *  2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
     */
    private transient volatile int sizeCtl;

    /**
     * 扩容过程中,记录当前进度。所有线程都需要从transferIndex中分配区间任务,去执行自己的任务。
     */
    private transient volatile int transferIndex;

    /**
     * LongAdder中的cellsBuzy 0表示当前LongAdder对象无锁状态,1表示当前LongAdder对象加锁状态
     */
    private transient volatile int cellsBusy;

    /**
     * LongAdder中的cells数组,当baseCount发生竞争后,会创建cells数组,
     * 线程会通过计算hash值 取到 自己的cell ,将增量累加到指定cell中
     * 总数 = sum(cells) + baseCount
     */
    private transient volatile CounterCell[] counterCells;

 静态代码块和静态变量



  // Unsafe mechanics private static final sun.misc.Unsafe U; /**表示sizeCtl属性在ConcurrentHashMap中内存偏移地址*/ private static final long SIZECTL; /**表示transferIndex属性在ConcurrentHashMap中内存偏移地址*/ private static final long TRANSFERINDEX; /**表示baseCount属性在ConcurrentHashMap中内存偏移地址*/ private static final long BASECOUNT; /**表示cellsBusy属性在ConcurrentHashMap中内存偏移地址*/ private static final long CELLSBUSY; /**表示cellValue属性在CounterCell中内存偏移地址*/ private static final long CELLVALUE; /**表示数组第一个元素的偏移地址*/ private static final long ABASE; private static final int ASHIFT; static { try { U = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentHashMap.class; SIZECTL = U.objectFieldOffset (k.getDeclaredField("sizeCtl")); TRANSFERINDEX = U.objectFieldOffset (k.getDeclaredField("transferIndex")); BASECOUNT = U.objectFieldOffset (k.getDeclaredField("baseCount")); CELLSBUSY = U.objectFieldOffset (k.getDeclaredField("cellsBusy")); Class<?> ck = CounterCell.class; CELLVALUE = U.objectFieldOffset (ck.getDeclaredField("value")); Class<?> ak = Node[].class; ABASE = U.arrayBaseOffset(ak); //表示数组单元所占用空间大小,scale 表示Node[]数组中每一个单元所占用空间大小 int scale = U.arrayIndexScale(ak); //1 0000 & 0 1111 = 0 底下的判断语句表示不是2的次方数就报错,java语言规范要求的 if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); //numberOfLeadingZeros() 这个方法是返回当前数值转换为二进制后,从高位到低位开始统计,看有多少个0连续在一块。 //8 => 1000 numberOfLeadingZeros(8) = 28 //以4为例 => 100 numberOfLeadingZeros(4) = 29 //ASHIFT = 31 - 29 = 2 ==>用来寻址 //ABASE + (5 << ASHIFT) ==> 就是5乘2的ASHIFT次方,就是5*scale,因为scale就是Node[]数组中每一个单元所占用空间大小 // 第五个元素的偏移量就是5*scale,ABASE + (5 << ASHIFT)就代表该元素的地址 ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (Exception e) { throw new Error(e); } }
  static final int MOVED     = -1; // hash for forwarding nodes 当前节点为FWD节点,扩容
    static final int TREEBIN   = -2; // hash for roots of trees 当前节点已经树化,且当前节点为TreeBin对象,TreeBin对象代理操作红黑树
    static final int RESERVED  = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash 将负数通过位与运算得到正数,但不是取绝对值

 

2 内部的一些小方法

  /**
     * 1100 0011 1010 0101 0001 1100 0001 1110      h
     * 0000 0000 0000 0000 1100 0011 1010 0101      h >>> 16
     * 1100 0011 1010 0101 1101 1111 1011 1011      运算结果
     * ---------------------------------------
     * 1100 0011 1010 0101 1101 1111 1011 1011      h ^ (h >>> 16)
     * 0111 1111 1111 1111 1111 1111 1111 1111      HASH_BITS
     * 0100 0011 1010 0101 1101 1111 1011 1011      运算结果:把附属改成正数
     */
    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

    /**
     * 返回>=c的最小的2的次方数
     * c=28
     * n=27 => 0b 11011
     * 11011 | 01101 => 11111
     * 11111 | 00111 => 11111
     * ....
     * => 11111 + 1 =100000 = 32
     */
    private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

3 构造函数

3.1 无参构造

使用默认值

3.2 参数为:int initialCapacity

  public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
    /**
      如果
initialCapacity大于MAXIMUM_CAPACITY的一半,则cap为MAXIMUM_CAPACITY,否则用该函数计算

     */
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); /** * sizeCtl > 0 * 当目前table未初始化时,sizeCtl表示初始化容量 */ this.sizeCtl = cap; }

3.3 参数为Map<? extends K, ? extends V> m

使用默认初始容量,并将m的值put进去。

3.4 参数为int initialCapacity, float loadFactor,两个

套娃了三个参数的构造函数

    public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
    }

3.5 三个参数的构造函数

  public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();

        //初始容量最小也不能小于并发级别
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads

        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        /**
         * sizeCtl > 0
         * 当目前table未初始化时,sizeCtl表示初始化容量
         */
        this.sizeCtl = cap;
    }

 4 put()方法

  public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //控制k 和 v 不能为null
        if (key == null || value == null) throw new NullPointerException();

        //通过spread方法,可以让高位也能参与进寻址运算。
        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 表示桶位头结点的hash值
            Node<K,V> f; int n, i, fh;

            //CASE1:成立,表示当前map中的table尚未初始化..
            if (tab == null || (n = tab.length) == 0)
                //最终当前线程都会获取到最新的map.table引用。
                tab = initTable(); //--①
            //CASE2:i 表示key使用路由寻址算法得到 key对应 table数组的下标位置,tabAt 获取指定桶位的头结点 f
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //进入到CASE2代码块 前置条件 当前table数组i桶位是Null时。
                //使用CAS方式 设置 指定数组i桶位 为 new Node<K,V>(hash, key, value, null),并且期望值是null
                //cas操作成功 表示ok,直接break,退出for循环即可
                //cas操作失败,表示在当前线程之前,有其它线程先你一步向指定i桶位设置值了,当前线程只能再次自旋,去走其它逻辑。
          //casTab函数里的参数:tab:要插入的表、索引值、期望值、新值——当表里的的索引位置的值与期望值相等,则把新值插入进去
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //CASE3:前置条件,桶位的头结点一定不是null。 //条件成立表示当前桶位的头结点 为 FWD结点,表示目前map正处于扩容过程中.. else if ((fh = f.hash) == MOVED) //看到fwd节点后,当前节点有义务帮助当前map对象完成迁移数据的工作 //学完扩容后再来看。 tab = helpTransfer(tab, f); //CASE4:当前桶位 可能是 链表 也可能是 红黑树代理结点TreeBin else { //当插入key存在时,会将旧值赋值给oldVal,返回给put方法调用处.. V oldVal = null; //使用sync 加锁“头节点”,理论上是“头结点” synchronized (f) { //为什么又要对比一下,看看当前桶位的头节点 是否为 之前获取的头结点? //为了避免其它线程将该桶位的头结点修改掉,导致当前线程从sync 加锁 就有问题了。之后所有操作都不用在做了。 if (tabAt(tab, i) == f) {//条件成立,说明咱们 加锁 的对象没有问题,可以进来造了! //条件成立,说明当前桶位就是普通链表桶位。 if (fh >= 0) { //binCount在链表里表示的两种情况:1.当前插入key与链表当中所有元素的key都不一致时,当前的插入操作是追加到链表的末尾,binCount表示链表长度 //2.当前插入key与链表当中的某个元素的key一致时,当前插入操作可能就是替换了。binCount表示冲突位置(binCount - 1)——为什么是binCount-1?因为binCount是从1开始的 binCount = 1; //迭代循环当前桶位的链表,e是每次循环处理节点。 for (Node<K,V> e = f;; ++binCount) { //当前循环节点 key K ek; //条件一:e.hash == hash 成立 表示循环的当前元素的hash值与插入节点的hash值一致,需要进一步判断 //条件二:((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; 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; } } } //前置条件,该桶位一定不是链表 //条件成立,表示当前桶位是 红黑树代理结点TreeBin else if (f instanceof TreeBin) { //p 表示红黑树中如果与你插入节点的key 有冲突节点的话 ,则putTreeVal 方法 会返回冲突节点的引用。 Node<K,V> p; //强制设置binCount为2,因为binCount <= 1 时有其它含义,所以这里设置为了2 binCount = 2; //条件一:成立,说明当前插入节点的key与红黑树中的某个节点的key一致,冲突了 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { //将冲突节点的值 赋值给 oldVal oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } //说明当前桶位不为null,可能是红黑树 也可能是链表 if (binCount != 0) { //如果binCount>=8 表示处理的桶位一定是链表 if (binCount >= TREEIFY_THRESHOLD) //调用转化链表为红黑树的方法 treeifyBin(tab, i); //说明当前线程插入的数据key,与原有k-v发生冲突,需要将原数据v返回给调用者。 if (oldVal != null) return oldVal; break; } } } //1.统计当前table一共有多少数据 //2.判断是否达到扩容阈值标准,触发扩容。 addCount(1L, binCount);//--② return null; }

--①初始化方法

    /**
     * Initializes table, using the size recorded in sizeCtl.
     *      * sizeCtl < 0
     *      * 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
     *      * 2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
     *      *
     *      * sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
     *      *
     *      * sizeCtl > 0
     *      *
     *      * 1. 如果table未初始化,表示初始化大小
     *      * 2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
     */
    private final Node<K,V>[] initTable() {
        //tab 引用map.table
        //sc sizeCtl的临时值
        Node<K,V>[] tab; int sc;
        //自旋 条件:map.table 尚未初始化
        while ((tab = table) == null || tab.length == 0) {

            if ((sc = sizeCtl) < 0)
                //大概率就是-1,表示其它线程正在进行创建table的过程,当前线程没有竞争到初始化table的锁。
                Thread.yield(); // lost initialization race; just spin

            //1.sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
            //2.如果table未初始化,表示初始化大小
            //3.如果table已经初始化,表示下次扩容时的 触发条件(阈值)
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    //这里为什么又要判断呢? 防止其它线程已经初始化完毕了,然后当前线程再次初始化..导致丢失数据。
                    //条件成立,说明其它线程都没有进入过这个if块,当前线程就是具备初始化table权利了。
                    if ((tab = table) == null || tab.length == 0) {

                        //sc大于0 创建table时 使用 sc为指定大小,否则使用 16 默认值.
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;

                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        //最终赋值给 map.table
                        table = tab = nt;
                        //n >>> 2  => 等于 1/4 n     n - (1/4)n = 3/4 n => 0.75 * n
                        //sc 0.75 n 表示下一次扩容时的触发条件。
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //1.如果当前线程是第一次创建map.table的线程话,sc表示的是 下一次扩容的阈值
                    //2.表示当前线程 并不是第一次创建map.table的线程,当前线程进入到else if 块 时,将
                    //sizeCtl 设置为了-1 ,那么这时需要将其修改为 进入时的值。
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

 --②addCount()方法

private final void addCount(long x, int check) {
        //as 表示 LongAdder.cells
        //b 表示LongAdder.base
        //s 表示当前map.table中元素的数量
        CounterCell[] as; long b, s;
        //条件一:true->表示cells已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据 --(1)
        //       false->表示当前线程应该将数据累加到 base
        //条件二:false->表示写base成功,数据累加到base中了,当前竞争不激烈,不需要创建cells
        //       true->表示写base失败,与其他线程在base上发生了竞争,当前线程应该去尝试创建cells。--(2)
      //注:这里的条件二是带了取反符号的
     if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { //有几种情况进入到if块中? //1.true->表示cells已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据 //2.true->表示写base失败,与其他线程在base上发生了竞争,当前线程应该去尝试创建cells。 //a 表示当前线程hash寻址命中的cell CounterCell a; //v 表示当前线程写cell时的期望值 long v; //m 表示当前cells数组的长度 int m; //true -> 未竞争 false->发生竞争 boolean uncontended = true; //条件一:as == null || (m = as.length - 1) < 0 //true-> 表示当前线程是通过 写base竞争失败 然后进入的if块,就需要调用fullAddCount方法去扩容 或者 重试(就相当于LongAdder.longAccumulate()方法)--是通过上面的(2)进来的 //条件二:a = as[ThreadLocalRandom.getProbe() & m]) == null 前置条件:cells已经初始化了 //true->表示当前线程命中的cell表格是个空,需要当前线程进入fullAddCount方法去初始化 cell,放入当前位置. //条件三:!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x) // false->取反得到false,表示当前线程使用cas方式更新当前命中的cell成功 // true->取反得到true,表示当前线程使用cas方式更新当前命中的cell失败,需要进入fullAddCount进行重试 或者 扩容 cells。 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(x, uncontended); //考虑到fullAddCount里面的事情比较累,就让当前线程 不参与到 扩容相关的逻辑了,直接返回到调用点。 return; }        /*check的取值:
        put()方法传进来时:>=1;==0;==2树化为红黑树        
        check if <0, don't check resize, if <= 1 only check if uncontended
        removve()方法传进来,check<0
        
       */
if (check <= 1) return; //获取当前散列表元素个数,这是一个期望值 s = sumCount(); }

//-------------------------扩容相关的-------------------//
//表示一定是一个put操作调用的addCount if (check >= 0) { //tab 表示map.table //nt 表示map.nextTable, nextTable表示扩容过程中,会将扩容中的新table 赋值给nextTable 保持引用,扩容结束之后,这里会被设置为Null
//n 表示map.table数组的长度
            //sc 表示sizeCtl的临时值
            Node<K,V>[] tab, nt; int n, sc;


            /**
             * sizeCtl < 0
             * 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待.. --不可能发生
             * 2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
             *
             * sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小 --不可能
             *
             * sizeCtl > 0
             *
             * 1. 如果table未初始化,表示初始化大小 --不可能
             * 2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
             */

            //自旋
            //条件一:s >= (long)(sc = sizeCtl)
            //       true-> 1.当前sizeCtl为一个负数 表示正在扩容中..
            //              2.当前sizeCtl是一个正数,表示扩容阈值
            //       false-> 表示当前table尚未达到扩容条件
            //条件二:(tab = table) != null
            //       恒成立 true
            //条件三:(n = tab.length) < MAXIMUM_CAPACITY
            //       true->当前table长度小于最大值限制,则可以进行扩容。
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {

                //扩容批次唯一标识戳
                //16 -> 32 扩容 标识为:1000 0000 0001 1011
                int rs = resizeStamp(n);

                //条件成立:表示当前table正在扩容
                //         当前线程理论上应该协助table完成扩容
                if (sc < 0) {
                    //条件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
                    //      true->说明当前线程获取到的扩容唯一标识戳 非 本批次扩容
                    //      false->说明当前线程获取到的扩容唯一标识戳 是 本批次扩容
                    //条件二: JDK1.8 中有bug jira已经提出来了 其实想表达的是 =  sc == (rs << 16 ) + 1
                    //        true-> 表示扩容完毕,当前线程不需要再参与进来了
                    //        false->扩容还在进行中,当前线程可以参与
                    //条件三:JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs<<16) + MAX_RESIZERS
                    //        true-> 表示当前参与并发扩容的线程达到了最大值 65535 - 1
                    //        false->表示当前线程可以参与进来
                    //条件四:(nt = nextTable) == null
                    //        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);//--③扩容
                }
                //rs << RESIZE_STAMP_SHIFT) + 2(以16扩容到32的唯一表示戳为例):1000 0000 0001 1011 0000 0000 0000 0000 +2 => 1000 0000 0001 1011 0000 0000 0000 0010 -->高16位表示唯一表示戳rs,低16位表示1+n个扩容的线程,加2里面的2表示是1+1,表示是第一个线程
//条件成立,说明当前线程是触发扩容的第一个线程,在transfer方法需要做一些扩容准备工作
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    //触发扩容条件的线程 不持有nextTable
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

 --③transfer()方法

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        //扩容是从下往上扩容得
        //n 表示扩容之前table数组的长度
        //stride 表示分配给线程任务的步长
        int n = tab.length, stride;
        //方便讲解源码  stride 固定为 16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range


        //条件成立:表示当前线程为触发本次扩容的线程,需要做一些扩容准备工作
        //条件不成立:表示当前线程是协助扩容的线程..
        if (nextTab == null) {            // initiating
            try {
                //创建了一个比扩容之前大一倍的table
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            //赋值给对象属性 nextTable ,方便协助扩容线程 拿到新表
            nextTable = nextTab;
            //记录迁移数据整体位置的一个标记。index计数是从1开始计算的。
            transferIndex = n;
        }

        //表示新数组的长度
        int nextn = nextTab.length;
        //fwd 节点,当某个桶位数据处理完毕后,将此桶位设置为fwd节点,其它写线程 或读线程看到后,会有不同逻辑。
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        //推进标记
        boolean advance = true;
        //完成标记
        boolean finishing = false; // to ensure sweep before committing nextTab

        //i 表示分配给当前线程任务,执行到的桶位
        //bound 表示分配给当前线程任务的下界限制
        int i = 0, bound = 0;
        //自旋
        for (;;) {
            //f 桶位的头结点
            //fh 头结点的hash
            Node<K,V> f; int fh;


            /**
             * 1.给当前线程分配任务区间
             * 2.维护当前线程任务进度(i 表示当前处理的桶位)
             * 3.维护map对象全局范围内的进度
             */
            while (advance) {
                //分配任务的开始下标
                //分配任务的结束下标
                int nextIndex, nextBound;

                //CASE1:
                //条件一:--i >= bound
                //成立:表示当前线程的任务尚未完成,还有相应的区间的桶位要处理,--i 就让当前线程处理下一个 桶位.
                //不成立:表示当前线程任务已完成 或 者未分配
                if (--i >= bound || finishing)
                    advance = false;
                //CASE2:
                //前置条件:当前线程任务已完成 或 者未分配
                //条件成立:表示对象全局范围内的桶位都分配完毕了,没有区间可分配了,设置当前线程的i变量为-1 跳出循环后,执行退出迁移任务相关的程序
                //条件不成立:表示对象全局范围内的桶位尚未分配完毕,还有区间可分配
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //CASE3:
                //前置条件:1、当前线程需要分配任务区间  2.全局范围内还有桶位尚未迁移
                //条件成立:说明给当前线程分配任务成功
                //条件失败:说明分配给当前线程失败,应该是和其它线程发生了竞争吧
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {

                    bound = nextBound;
                    //为什么要-1? 因为nextIndex是从1开始计算的,而i是从0开始
                    i = nextIndex - 1;
                    advance = false;
                }
            }

            //CASE1:
            //条件一:i < 0
            //成立:表示当前线程未分配到任务
            if (i < 0 || i >= n || i + n >= nextn) {
                //保存sizeCtl 的变量
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    //是扩容后数组大小的0.75:(n << 1) - (n >>> 1) = 2n - n/2 = 3/2 n = 0.75 (2n)
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //因为进来的时候sc+1了,
                //条件成立:说明设置sizeCtl 低16位  -1 成功,当前线程可以正常退出
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    //1000 0000 0001 1011 0000 0000 0000 0000
                    //条件成立:说明当前线程不是最后一个退出transfer任务的线程,因为第一个线程进来是1000 0000 0001 1011 0000 0000 0000 0010
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        //正常退出
                        return;
                    //将finishing和advance设为true就可以退出while循环了
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //前置条件:【CASE2~CASE4】 当前线程任务尚未处理完,正在进行中

            //CASE2:
            //条件成立:说明当前桶位未存放数据,只需要将此处设置为fwd节点即可。
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            //CASE3:
            //条件成立:说明当前桶位已经迁移过了,当前线程不用再处理了,直接再次更新当前线程任务索引,再次处理下一个桶位 或者 其它操作
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            //CASE4:
            //前置条件:当前桶位有数据,而且node节点 不是 fwd节点,说明这些数据需要迁移。
            else {
                //sync 加锁当前桶位的头结点
                synchronized (f) {
                    //防止在你加锁头对象之前,当前桶位的头对象被其它写线程修改过,导致你目前加锁对象错误...
                    if (tabAt(tab, i) == f) {
                        //ln 表示低位链表引用
                        //hn 表示高位链表引用
                        Node<K,V> ln, hn;

                        //条件成立:表示当前桶位是链表桶位
                        if (fh >= 0) {


                            //lastRun
                            //可以获取出 当前链表 末尾连续高位不变的 node
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }

                            //条件成立:说明lastRun引用的链表为 低位链表,那么就让 ln 指向 低位链表
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            //否则,说明lastRun引用的链表为 高位链表,就让 hn 指向 高位链表
                            else {
                                hn = lastRun;
                                ln = null;
                            }



                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }



                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        //条件成立:表示当前桶位是 红黑树 代理结点TreeBin
                        else if (f instanceof TreeBin) {
                            //转换头结点为 treeBin引用 t
                            TreeBin<K,V> t = (TreeBin<K,V>)f;

                            //低位双向链表 lo 指向低位链表的头  loTail 指向低位链表的尾巴
                            TreeNode<K,V> lo = null, loTail = null;
                            //高位双向链表 lo 指向高位链表的头  loTail 指向高位链表的尾巴
                            TreeNode<K,V> hi = null, hiTail = null;


                            //lc 表示低位链表元素数量
                            //hc 表示高位链表元素数量
                            int lc = 0, hc = 0;

                            //迭代TreeBin中的双向链表,从头结点 至 尾节点
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                // h 表示循环处理当前元素的 hash
                                int h = e.hash;
                                //使用当前节点 构建出来的 新的 TreeNode
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);

                                //条件成立:表示当前循环节点 属于低位链 节点
                                if ((h & n) == 0) {
                                    //条件成立:说明当前低位链表 还没有数据
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    //说明 低位链表已经有数据了,此时当前元素 追加到 低位链表的末尾就行了
                                    else
                                        loTail.next = p;
                                    //将低位链表尾指针指向 p 节点
                                    loTail = p;
                                    ++lc;
                                }
                                //当前节点 属于 高位链 节点
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }



                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

 

posted @ 2020-10-27 17:28  Mistolte  阅读(143)  评论(0)    收藏  举报