Чтение исходного кода ConcurrentHashMap

исходный код

Чтение исходного кода ConcurrentHashMap

稍微有点粗糙的图片

Немного грубая картина

  • ConcurrentHashMapОн принадлежит к параллельному пакету Java и может быть назван потокобезопасным.HashMap.(именуемый в дальнейшемCHM)

  • хорошо известно,HashMapИмеет хорошую производительность доступа, но не поддерживает параллельные среды,HashTableПоддержка параллельной среды и прямое добавление метода доступаSynchronizedспособ значительно снизит производительность, хотяSynchronizeсуществуетJDK1.6После большой оптимизации, но все еще не самое лучшее.

  • существуетHashMapсерединаМассив + связанный список/красно-черное деревоПо строению отличается отHashTableблокирует весь объект массива вConcurrentHashMapИспользуя механизм блокировки каждого бакета в массиве, я не знаю, может ли он стать блокировкой сегмента.JDK1.7Сегмент используется, хотя и сохранен в коде 1.8, он очень короткий и только для совместимости)

  • Это только личное мнение, если есть какие-либо ошибки, пожалуйста, оставьте сообщение, спасибо!


Переменные-члены

1. transient volatile Node<K,V>[] table;

Массив узлов, который фактически хранит данные,volatileГарантированная видимость.

2. private transient volatile Node<K,V>[] nextTable;

Следующий используемый массив не пуст только при обновлении расширения, и данные будут медленно перемещаться в этот массив во время расширения.

Массив чрезмерно расширен и не может быть доступен вне класса

3. private transient volatile long baseCount;

Статистика элемента при отсутствии конкуренции

4. private transient volatile int transferIndex;

Значение индекса расширения указывает позицию индекса массива таблиц, который был выделен потоку расширения, который в основном используется для координации безопасности параллелизма задач миграции между несколькими потоками.

private transient volatile int sizeCtl;

сопоставимый по важностиAQSизstate, является переменной гонки, совместно используемой несколькими потоками, используемой для поддержания различных состояний и сохранения различной информации.

  • sizeCtl > 0можно разделить на две ситуации:

    • Когда не инициализирован,sizeCtlпредставляет начальную емкость.
    • После инициализации он представляет собой порог расширения, т.Длина текущей длины массива*0,75
  • sizeCtl = -1: Указывает, что он находится в фазе инициализации или расширения.

  • sizeCtl < -1 : sizeCtlпредпринять расширениеидентификатор(старшие 16 бит) иКоличество участвующих потоков(младшие 16 бит) хранение

    • существуетaddCountа такжеhelpTransferВ коде метода ,Если вам нужна помощь в масштабировании, CAS будет заменен наsizeCtl+1

    • Когда текущее содержимое расширения завершено и нет перераспределенной области, поток выйдет из расширения, а CAS будет заменен наsizeCtl-1

Node
  • Класс узла, который составляет элементы связанного списка и сохраняет пару ключ-значение K/V.
 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }
        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }
      	/**
         * Virtualized support for map.get(); overridden in subclasses.
         * 为map.get()提供虚拟化支持;在子类中覆盖.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    // 里面就是普通的链表循环,直到拿到相应的值
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }
  • а такжеConcurrentHashMap,HashMapсерединаNodeразница
    • valа такжеnextиспользоватьvolatileОформление ключевых слов для обеспечения видимости между несколькими потоками.
    • hashCodeПодход немного отличается, потому чтоConcurrentHashMapне поддерживаетсяkeyилиvalueявляется значением NULL, поэтому используйте его напрямуюkey.hashCode() ^ val.hashCode()Пустое суждение пропускается.
  • find()Этот метод используется, чтобы помочь получить элементы за узлом в определенный период времени.Как правило, он вызывается как головной узел ведра для запроса элементов в ведре.
ForwardingNode
  • узел пересылки

  • Этот класс выживает только в фазе расширения и помещается вверху ведра в качестве узла-маркера и указывает наnextTable(расширенный промежуточный массив)

  • Как видно из конструктора,ForwardingNodeизhashЭто -1, остальные пустые, это полный вспомогательный класс.

static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
    
    	// 构造函数中默认以MOVED:-1为Hash,其它为空
        ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null, null);
            this.nextTable = tab;
        }
    	// 帮助扩容时的元素查找
        Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;
                for (;;) {
                    int eh; K ek;
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        else
                            return e.find(h, k);
                    }
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }
    }
  • find()Метод действительно помогает во время расширенияgetметод для получения элементов в ведре.

Как добавить элементы

 	public V put(K key, V value) {
        return putVal(key, value, false);
    }
  • По соглашению наиболее уязвимые методы — это методы логической реализации, которые вызываются напрямую.
Конкретный логический метод хранения putVal
    /**
	 * 方法参数:
	 * 1. key,value 自然不用说就是k/v的两个值
	 * 2. onlyIfAbsent 若为true,则仅仅在值为空时覆盖
	 * 返回值:
	 *  返回旧值,若是新增就为null.
	 */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // CHM不支持NULL值的铁证.
        if (key == null || value == null) throw new NullPointerException();
        // 获得key的Hash,spread可以称之为扰动函数
        int hash = spread(key.hashCode());
        int binCount = 0;
        // 无限循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 在tab为空时负责初始化Table
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 使用`(n-1)&hash`确定了元素的下标位置,获取对应节点
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 如果对应位置节点为空,直接以当前信息为桶的头节点
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 如果获取的桶的头结点的`Hash`为`MOVED`,表示该节点是`ForwardingNode`
            // 也就表示数组正在进行扩容
            else if ((fh = f.hash) == MOVED)
                // 帮助扩容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 上锁保证原子性,volatile仅能保证可见性
                // f为key获取到的节点元素,以此为锁对象
                synchronized (f) {
                    // f在上文就是根据`tabAt(tab,i)`获取的
                    // 此处是再次获取验证有没有被修改
                    if (tabAt(tab, i) == f) {
                        // 与else.if比较,得知
                        // fh >= 0表示当前节点为链表节点,即当前桶结构为链表 		  ???
                        if (fh >= 0) {
                            // 链表中的元素个数统计
                            binCount = 1;
                            // 循环遍历整个桶
                            // 跳出循环的两种情况:
                            // 1. 找到相同的值,binCount此时表示遍历的节点个数
                            // 2. 遍历到末尾,binCount就表示桶中的节点个数
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                // 源码中大量运用了表达式的短路特性,来展示判断的优先级
                                // 1. 若hash不相等,则直接跳过判断
                                // 2. hash相等之后,若key的地址相同,则直接进入if
                                // 3. 地址不同时在进入判断内容是否相等
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    // onlyIfAbsent为true,表示存在时不覆盖内容
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    // 已经找到确定的元素了,更新不更新都跳出
                                    break;
                                }
                                // 因为e就在同步代码块中,桶已经被上锁,不可能有别的线程改变
                                // 所以不需要重新获取
                                Node<K,V> pred = e;
                                // 1. 如果e为空,则直接将元素挂接到e后面,跳出循环
                                // 2. e不为空,继续遍历
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        // 类似HashMap,树节点独立操作.
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // 表示进入了上面的同步表达式,对桶进行修改之后
                if (binCount != 0) {
                    // 如果binCount大于树的临界值,就将链表转化为红黑树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    // 如果oldVal部位空,则返回
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        // 添加元素计数,并在binCount大于0时检查是否需要扩容
        addCount(1L, binCount);
        return null;
    }
Весь процесс добавления
  1. Судя и исключая ключ, значение не пусто,ConcurrentHashMapНулевой ключ или значение не поддерживается.

  2. Получите возмущенный хэш, введите обход массива вкладок и инициализируйте, если массив пуст.

  3. пройти через(n - 1) & hashФормула для получения нижнего индекса ведра, если ведро пусто, введите ключ напрямую, а значение является головным узлом ведра.

  4. Определить хеш головного узла корзины, еслиhash == -1выражатьАссортимент расширяется и помогает расширяться.

  5. ВойтиsynchronizeБлок синхронизированного кода, еслиЕсли хэш головного узла корзины больше 0, это означает, что структура корзины представляет собой связанный список., за которым следует обычный обход связанного списка, добавление или перезапись.

  6. еслиГоловной узел ковшаTreeBinТип указывает на то, что структура ведра представляет собой красно-черное дерево., обход которого осуществляется по операции красно-черного дерева.

  7. Выйдите из блока кода синхронизации и определите статистику во время обходаbinCountНужно ли его преобразовать в красно-черную древовидную структуру.

  8. судитьoldValПустой он или нет, этот шаг тоже очень критичен.Если он не пустой, то операция перезаписывается, прямоreturnЭто нормально, не нужно проверять расширение.

  9. еслиoldValНе для кондиционераaddCountМетод добавляет количество элементов и определяет, требуется ли расширение.

Метод получения элемента

   public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
       	// 获取hash,并进过扰动
        int h = spread(key.hashCode());
       	// 判断以进入获取方法
       	// 1. 数组不为空 & 数组长度大于0
        // 2. 获取的桶不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            // 获取桶下标的公式都是通用的 `(n -1) & h`
            (e = tabAt(tab, (n - 1) & h)) != null)
        {// 对于桶中头节点的hash,对比成功就不需要遍历整个列表了
            if ((eh = e.hash) == h) {
                // 返回匹配的元素value
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 元素hash < 0的情况有以下三种:
            // 1. 数组正在扩容,Node的实际类型是ForwardingNode
            // 2. 节点为树的root节点,TreeNode
            // 3. 暂时保留的Hash, Node
            // 不同的Node都会调用各自的find()方法
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            // 如果头节点不是所需节点,且Map此时并未扩容
        	// 直接遍历桶中元素查找
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
Полный процесс приобретения выглядит следующим образом:
  1. Получено функцией возмущенияkeyХэш, прежде чем его получить, сначала оценит, пуста ли вкладка и ее длина
  2. пройти через(n -1)& hashПолученные сегменты В следующей таблице получены сегменты.
  3. судитьkeyНезависимо от того, равен ли хэш ведра головному узлу ведра, он будет возвращен напрямую, если они равны.
  4. Если полученный узел головки ковшаhash < 0,ВыражатьВ следующих трех состояниях фактический тип узла вызывается вызовомfindметод получения элемента.
    1. Массив расширяется, фактический тип узлаForwardingNode
    2. Узел является корневым узлом дерева, а тип узла —TreeNode
    3. Временно зарезервированный хеш, узел
  5. Если хэши не равны и хэш головного узла нормальный, тоЭто обычная операция поиска по связному списку.

Механизм расширения

  • Я должен сказать, что код части расширения определенно супер-первоклассный мастер!!!
Мониторинг расширения addCount
  • addCountРоль:
    1. УвеличиватьConcurrentHashMapколичество элементов
    2. Необходимо ли расширить обнаружение прекурсоров,
/**
 *   参数: 
 * 	 x -> 具体增加的元素个数
 *   check -> 如果check<0不检查时都需要扩容,
 */
private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
     	// 1. counterCells不为空
      	// 2. CAS修改baseCount属性成功
        if ((as = counterCells) != null ||
            // CAS增加baseCOunt
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            // 线程争用的状态标记
            boolean uncontended = true;
            // 1. 计数cell为null,或长度小于1
            // 2. 随机去一个数组位置为为空
            // 3. CAS替换CounterCell的value失败
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            // CAS增加CounterCell的value值失败会调用fullAddCount方法
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
    	// 根据`check >= 0`判断是否需要检查扩容
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            // 1. 如果元素总数大于sizeCtl,表示达到了扩容阈值
            // 2. tab数组不能为空,已经初始化
            // 3. table.length小于最大容,有扩容空间
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                // 根据数组长度获取一个扩容标志
                int rs = resizeStamp(n);
                if (sc < 0) {
                    // 如果sc的低16位不等于rs,表示标识符已经改变.				// 待补充
                    // 如果nextTable为空,表示扩容已经结束
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    // CAS替换sc值为sc+1,成功则开始扩容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        //	调用transfer开始扩容,此时nextTable已经指定
                        transfer(tab, nt);
                }
                // `sc > 0`表示数组此时并不在扩容阶段,更新sizeCtl并开始扩容
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // 调用transfer,nextTable待生成
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }
помощьПомощь по переводу развернуть
 /**
  * 参数:
  * tab -> 扩容的数组,一般为table
  * f -> 线程持有的锁对应的桶的头节点
  * 调用地方:
  * 1. `putVal`检测到头节点Hash为MOVED
  */
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        // 1.参数数组不能为空 
		// 2.参数f必须为ForwardingNode类型
        // 3.f.nextTab不能为空
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            // resizeStamp一顿位操作打的我头昏脑涨
            // 获取扩容的标识
            int rs = resizeStamp(tab.length);
            // Map仍处在扩容状态的判断
            // 1. 判断节点f的nextTable是否和成员变量的nextTable相同
            // 2. 判断传入的tab和成员变量的table是否相同
            // 3. sizeCtl是否小于0
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                // 两种不同的情况判断                
                // 一. 不需要帮助扩容的情况
                // 1. sc的高16位不等于rs
                // 2. sc等于rs+1
                // 3. sc等于rs+MAX_RESIZERS
                // 4. transferIndex <= 0, 这个好理解因为扩容时会分配并减去transferIndex,
                // 小于0时表示数组的区域已分配完毕
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                // 二. CAS `sc+1`并调用transfer帮助扩容.
                // 线程在帮助扩容时会对sizeCtl+1,完成时-1,表示标记
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }
Основной метод расширения переноса отвечает за миграцию элементов в корзине.
  private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
      	// stride为此次需要迁移的桶的数目
      	// NCPU为当前主机CPU数目
      	// MIN_TRANSFER_STRIDE为每个线程最小处理的组数目
      	// 1. 在多核中stride为当前容量的1/8对CPU数目取整,例如容量为16时,CPU为2时结果是1
      	// 2. 在单核中stride为n就为当前数组容量
 		// !!! stride最小为16,被限定死.
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
      	// nextTab是扩容的过渡对象,所以必须要先初始化
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                // !!! 重点就在这 扩容后的大小为当前的两倍 --> n << 1
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
            	// 扩容失败,直接填充int的最大值
                sizeCtl = Integer.MAX_VALUE;
                // 直接退出
                return;	
           }
            // 更新成员变量
            nextTable = nextTab;
            // transferIndex为数组长度
            transferIndex = n;
        }
      	// 记录过渡数组的长度
        int nextn = nextTab.length;
    	// 此处新建了一个ForwardingNode用于后续占位
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        /**
          * 以上为数据准备部分,初始化过渡数组,记录长度,创建填充节点等操作
          * 以下时真正扩容的主要逻辑
          */
 		// 该变量控制迁移的进行,     
        boolean advance = true;
        boolean finishing = false; 			// 两个变量作用未知 finishing可能是此次扩容标记
  // 扩容的for循环里面可以分为两部分
 // 一. while循环里面确定需要迁移的桶的区域,以及本次需要迁移的桶的下标
      	// 这个i就是需要迁移的桶的下标
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
          	// 该while代码块根据if的顺序功能分别是
            // --i: 负责迁移区域的向前推荐,i为桶下标
            // nextIndex: 在没有获取负责区域时,检查是否还需要扩容
            // CAS: 负责获取此次for循环的区域,每次都为stride个桶
            while (advance) {
                int nextIndex, nextBound;
                // 这个`--i`每次都会进行,每次都会向前推进一个位置
                if (--i >= bound || finishing)
                    advance = false;
                // 因此如果当transferIndex<=0时,表示扩容的区域分配完
                else if ((nextIndex = transferIndex) <= 0) {
            		i = -1;
                    advance = false;
                // CAS替换transferIndex的值,新值为旧值减去分到的stride
                // stride就表示此次的迁移区域,nextIndex就代表了下次起点
                // 从这里可以看出扩容是从数组末尾开始向前推进的
                }else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    // bount为此次扩容的推进终点,下次起点
                    bound = nextBound;
                    // i此次扩容开始的桶下表
                    i = nextIndex - 1;
                    advance = false;
                }
            }
 // 二. 扩容的逻辑代码
        // 1. 此if判定扩容的结果,中间是三种异常值
              // 1). i < 0的情况时上面第二个if跳出的线程
          	  // 2). i > 旧数组的长度
           	  // 3). i+n大于新数组的长度
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                // 此阶段扩容结束后的操作
                // 1. 将nextTable置空,
                // 2. 将中间过渡的数组赋值给table
                // 3. sizeCtl变为1.5倍(2n-0.5n)
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    // 分别使用有符号左移,无符号右移
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                // CAS替换`sizeCtl-1`,表示本线程的扩容任务已经完成
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    //	表达式成立表示还有别的线程在执行扩容,直接退出
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    // 表达式成立,表示已经全部扩容完成.
                    finishing = advance = true;
                    // 提交前重新检查
                    i = n; 
                }
            }
       // 2. 扩容时发现负责的区域有空的桶直接使用ForwardingNode填充
            // ForwardingNode持有nextTable的引用
            else if ((f = tabAt(tab, i)) == null)
                // CAS替换
                advance = casTabAt(tab, i, null, fwd);
       // 3. 表示处理完毕
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
      // 4. 迁移桶的操作
            else {
                // sync保证原子性和可见性
                synchronized (f) {                
                    // 获取数组中的第i个桶的头节点
                    // 进入synchronized之后重新判断,保证数据的正确性没有在中间被修改
                    if (tabAt(tab, i) == f) {
                        // 此处扩容和HashMap有点像,分为了lowNode和highNode两个头结点
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            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;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            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);
                           	// true的话会重新
                            advance = true;
                        }
                        // 树的桶迁移操作
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                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;
                                    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;
                        }
                    }
                }
            }
        }
    }

Расширение запускает логику потока
  1. существуетaddCountВ середине метода проверяем, достигает ли количество элементов порога раскрытия (0,75 * table.length), если оно превышает, срабатывает раскрытие и вызывается методtransfer.

    • Обратите внимание, что в это времяsizeCtlБудетCASзаменить(resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2
  2. Далееteansferкод:

    1. согласно сCPUРассчитайте размер выделенной области для каждого расширения с текущей емкостью, минимум 16, что выражается какstride.

    2. Если переходный массивnextTabЕсли он не инициализирован, сначала инициализируйте массив.transferIndexЗапишите длину старого массива как длину расширения.

    3. Данные, необходимые для вышеуказанного расширения, полностью подготовлены для запуска конкретной операции расширения:

    4. вwhileДиапазон ведер, входящих в это расширение, получается в цикле, т. е.[transferIndex,transferIndex-stride]спектр,iУказывает индекс расширяемого в данный момент сегмента.

    5. Три суждения, четыре фрагмента кода для выполнения операции в разных ситуациях соответственно

      1. iаномальное значение,< 0 || >= n || + n > nextn, указывая на то, что расширение завершено, иwhileЗадачи расширения, которые не назначены в цикле.

        • если в это времяfinishingПараметрыtrueУказывает, что общее расширение завершено, и проверка перед завершением завершена.

        • еслиfinishingзаfalse,но**CASЗамените sizeCtl на sizeCtl-1**, указывая на то, что поток завершил задачу расширения и должен выйти.

          После успешной замены он также проверитscРавен ли онaddCountЗначение, когда оно приходит, если оно не равно, оно напрямуюreturn, указывая на то, что еще есть потоки, не завершившие задачу расширения.

      2. iСоответствующее ведро пусто, используйте его напрямуюForwardingNodeЗаполните головной узел, указав, что емкость здесь расширяется.advanceзаtrue

      3. Если узел проверенhashзаMovedУказывает, что текущий узелForwardingNode,advanceзаtrue.

      4. Вышеупомянутые три случая исключены, то есть миграция соответствующего бакета, иHashMapвроде как поставил после концаadvanceзаtrue

    6. Затем мы вернемся к шагу 4.

Логика масштабирования ведомого потока
  1. существуетputValВ способе работы других элементов установлено, что полученный узел головки ковша являетсяForwdingNodeозначаетConcurrentHashMapВ настоящее время он расширяется и будет немедленно вызванhelpTransferПомогите расширить.
  2. helpTransferБудут различные суждения о правильности, и только при выполнении следующих трех условий это поможет расшириться.
    1. tabне пусто
    2. Является ли головной узелForwardingNode
    3. массив переходовnextTableИнициализировать ли.
  3. Цикл while имеет следующие два суждения
    1. Чтобы определить, нуждается ли процесс расширения в помощи, существуют следующие пять ситуаций, в которых помощь не требуется.
      1. sc >> 16 != rs- Идентификатор изменился.
      2. sc == rs+1- Поток, вызвавший расширение, завершился, и расширение было завершено.
      3. sc == rs+MAX_RESIZER- Количество потоков, участвующих в расширении, достигает максимального значения
      4. transferIndex <= 0- Площадь расширения была выделена
    2. За исключением вышеперечисленных ситуаций, не требующих помощи, вызоветtransferПомогите расширить.
Изменения sizeCtl во время расширения
  1. addCount -> sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2

    • Вот момент, который я давно выяснял: почему вhelpTransferбудет судитьsizeCtl16-битные операции высокого порядка,
    • Присвоение значения здесь эквивалентно присвоениюresizeStamp(n)Значение увеличивается на 16 бит и присваиваетсяsizeCtl, а младшие 16 бит экономят эти 2. То есть при расширении разрядностиsizeCtlСтаршие 16 бит содержат идентификатор, а младшие 16 бит содержат количество участвующих потоков.
    • Какой чертов гений.

  2. Есть темы, участвующие в расширении ->sizeCtl = sizeCtl - 1

  3. Расширение выхода из потока ->sizeCtl = sizeCtl + 1

  4. Расширение завершено ->sizeCtl = nextTab.length * 0.75

метод инициализации

  • а такжеHashMapТакой же,ConcurrentHashMapВместо того, чтобы инициализировать базовый массив непосредственно в конструкторе,putВ методе хранения ожидания определите, требуется ли расширение.
Функция инициализации массива initTable
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        // `sizeCtl`表示有别的数组正在初始化,让出CPU时间
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        // CAS操作,以-1置换`sizeCtl`的值
        // 可以看出 `sizeCtl==-1`时,表示数组正在某个线程初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 置换之后需要重新检测数组是否未初始化
                if ((tab = table) == null || tab.length == 0) {
                    // sc就是置换之前的sizeCtl.
                    // 此时sizeCtl作为初始容量.
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // 初始化结束之后sc变为0.75n,是扩容阈值
                    sc = n - (n >>> 2);
                }
            } finally {
                // 为避免异常退出导致sizeCtl永久为-1,此处强制赋值.
                sizeCtl = sc;
            }
            break;
        }
    }
    // 返回了新建的数组地址
    return tab;
}
  • initTableметод вputValа не функция-конструкторCHMЛенивый механизм загрузки в .
  • Процесс инициализации:
    1. Проверьте, инициализируются ли другие потоки, откажитесь от кванта времени, если он есть, и перейдите к следующему шагу, если нет.
    2. Перед инициализацией установитеsizeCtlпройти черезCASЗаменено на -1, что указывает на то, что он инициализируется
    3. кsizeCtlПредыдущее значение — начальная емкость,sizeCtl
    4. инициализация завершена,будетsizeCtlНазначение составляет 0,75*емкость массива.(sizeCtl проходит через всю статью, это действительно важно)

общий инструментальный подход

1. resizeStamp Получает отметку при расширении
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }
  • Integer.numberOfLeadingZeros(n) Возвращает количество ведущих нулей в 32-битной двоичной форме числа n., например значение бита 16int(32位)Бинарный тип представлен как000000...0010000, перед 1 стоит 27 нулей, а в конце 27.
  • |Теперь операцию можно понимать здесь просто как сложение.
  • Эффект интеграции:Получите количество нулей перед значащими битами n плюс 1 в 15-й степени.
  • Непонятно, зачем вы хотите получить штамп
2. Распределенная функция возмущения
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
  • функция возмущения иHashMapсерединаhash()Аналогично действует метод.
  • CHMПомимо операции XOR между старшими 16 битами и младшими 16 битами, функция возмущения в HASH_BITS,Это может эффективно снизить вероятность столкновения хэшей и сделать распределение элементов более равномерным.

Метод доступа к элементам массива узлов

1. tabAt получает элементы массива изменчивым способом
    @SuppressWarnings("unchecked")
	// tab: 数组   i : 下标
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }
2. КАСТАБАТCASФорма заменяет элементы массива
// tab: 原始数组  i:下标 c:对比元素 v:替换元素   
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
3. setTabAt обновляет элементы массива энергозависимым образом.
// tab:原始数组 i:下标 v:替换元素
	static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

Небезопасный статический блок

  • UnsafeЭто область, которую разработчики Java редко затрагивают, но вот краткое понимание
    private static final sun.misc.Unsafe U;
	// sizeCtl属性的偏移地址
    private static final long SIZECTL;
	// transferIndex属性的偏移地址
    private static final long TRANSFERINDEX;
	// baseCount的偏移地址
    private static final long BASECOUNT;
	// cellsBusy的偏移地址
    private static final long CELLSBUSY;
	// CounterCell类中value的偏移地址
    private static final long CELLVALUE;
	// Node数组第一个元素的偏移地址
    private static final long ABASE;
	// Node数组中元素的增量地址,与ABASE配合使用能访问到数组的各元素
    private static final int ASHIFT;

    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            // 先通过反射获取到对应的属性值,再通过Unsafe类获取属性的偏移地址
            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);
            // 获取数组的增量地址
            int scale = U.arrayIndexScale(ak);
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }
    }