HashMap&&ConcurrentHashMap

JDK1.7:

HashMap

创建节点

   void createEntry(int hash, K key, V value, int bucketIndex) {
//        采取头插入法,1:创建新的Entry节点,next节点指向e, 2:新Entry节点赋值给数组中的位置
        java.util.HashMap.Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new java.util.HashMap.Entry<>(hash, key, value, e);
        size++;
    }
复制代码
public V put(K key, V value) {
        // 第一次put  进行初始化
        if (table == EMPTY_TABLE) {
            // 默认数组大小 16  传进去初始化大小 如果不是2的整数次幂,会以最近的大于2的整数次幂的大小作为容量
            inflateTable(threshold);
        }
        // key 可以为null
        if (key == null)
// 这里会把key为null的数据都放到table[0]的位置
return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); // 这里的遍历是 数组的某个索引位开始的链表,如果和当前存入的key相同,就覆盖当前的value值 然后返回被覆盖的value 结束 for (java.util.HashMap.Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
复制代码
复制代码
 private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        // 使用 Integer.highestOneBit(int num) 进行位运算 ,但是这个函数返回的是小于等于参数的2的整数次幂
        /** 通过右移位运算和或运算 把原数据的最高位保留,其他为全部为0
         *
         * public static int highestOneBit(int i) {
         // HD, Figure 3-1
         i |= (i >>  1);
         i |= (i >>  2);
         i |= (i >>  4);
         i |= (i >>  8);
         i |= (i >> 16); 执行到这里原数据  由  0000  1*** 变成  0000 1111
         return i - (i >>> 1);  这里直接 0000 1111 - 0000 0111
         }
         *
         */
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new java.util.HashMap.Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
复制代码

 扩容:

复制代码
/**
     * Adds a new entry with the specified key, value and hash code to
     * the specified bucket.  It is the responsibility of this
     * method to resize the table if appropriate.
     *
     * Subclass overrides this to alter the behavior of put method.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 扩容  数据长度>= table.length*加载因子 并且  要放入的位置元素不为空的时候才会扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            // 扩容 直接两倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
复制代码

 

复制代码
void resize(int newCapacity) {
        java.util.HashMap.Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 直接创建了一个新的数组
        java.util.HashMap.Entry[] newTable = new java.util.HashMap.Entry[newCapacity];
        // 进行新老数组的转移  initHashSeedAsNeeded 是否使用hash种子 来进行hash算法让结果更散列一些
        // 可以指定 jdk.map.althashing.threshold 环境变量为 n,当数组容量大于n的时候 可以使用hash种子
        // 默认是hash种子是0,当使用hash种子的时候 扩容的时候hash肯定要重写计算的  但是通常hash种子为0 扩容的时候是不会
        // 重写计算hash值的
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
复制代码
复制代码
/**
     * Transfers all entries from current table to newTable.
     */
    void transfer(java.util.HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        // 双层遍历 一层是table  一层是链表  链表遍历的时候放到新的数组里面是采用头插法,所以造成新的存储顺序和原来不一样,顺序反了
        // 这里会有并发造成闭环的问题
        for (java.util.HashMap.Entry<K,V> e : table) {
            while(null != e) {
                java.util.HashMap.Entry<K,V> next = e.next;
                // 是否要重新hash 是不一定的  在JDK1.8中就没有这个过程了
                // 和是否有hash种子有关
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
复制代码
复制代码
  /**
     * Initialize the hashing mask value. We defer initialization until we
     * really need it.
     */
    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= java.util.HashMap.Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                    ? sun.misc.Hashing.randomHashSeed(this)
                    : 0;
        }
        return switching;
    }
复制代码

JDK1.7的闭环和丢失的问题可以参见:https://www.pianshen.com/article/42821264881/

ConcurrentHashMap

ConcurrentHashMap 采用分段锁(只是一个概念),hashmap中的每个数组都是一个Entry对象。但是在ConcurrentHashMap 中,数组中存的是 Segment对象,每个 Segment 中有都有一个 HashEntry[]的数组。

构造函数:
复制代码
/**
     * Creates a new, empty map with a default initial capacity (16),
     * load factor (0.75) and concurrencyLevel (16).
     * DEFAULT_INITIAL_CAPACITY:默认 16  想要一共存储多少个元素,即所有Segment中的 HashEntry 数组有多少长度
     * DEFAULT_CONCURRENCY_LEVEL:并发等级,默认16,决定Segment数组的大小,不小于此数据的最小2的幂次数
     * 所以构造函数中会根据  DEFAULT_INITIAL_CAPACITY / DEFAULT_CONCURRENCY_LEVEL 计算每个Segment中的 HashEntry数组多大
     * 但是又不是完全根据上面的 /  结果
     */
    public ConcurrentHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }
复制代码
构造函数:
复制代码
/**
     * Creates a new, empty map with the specified initial
     * capacity, load factor and concurrency level.
     *
     * @param initialCapacity the initial capacity. The implementation
     * performs internal sizing to accommodate this many elements.
     * @param loadFactor  the load factor threshold, used to control resizing.
     * Resizing may be performed when the average number of elements per
     * bin exceeds this threshold.
     * @param concurrencyLevel the estimated number of concurrently
     * updating threads. The implementation performs internal sizing
     * to try to accommodate this many threads.
     * @throws IllegalArgumentException if the initial capacity is
     * negative or the load factor or concurrencyLevel are
     * nonpositive.
     */
    @SuppressWarnings("unchecked")
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 用来决定每个HashEntry数组有多大
        int c = initialCapacity / ssize;
        // 向上取整
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        // 就算向上取整  也不一定能满足要求 因为HashEntry的数组长度也要是2的幂次数
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        // 这里创建一个Segment对象 然后通过unsafe方法放到  Segment<K,V>[] ss 数组中的第一个位置
        // 这样做是为了后面其他位置创建Segement对象的时候 对于阈值和HashEntry的大小就不用重复计算了,直接在s0中就可以得到
        java.util.concurrent.ConcurrentHashMap.Segment<K,V> s0 =
                new java.util.concurrent.ConcurrentHashMap.Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                        (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[])new java.util.concurrent.ConcurrentHashMap.HashEntry[cap]);
        java.util.concurrent.ConcurrentHashMap.Segment<K,V>[] ss = (java.util.concurrent.ConcurrentHashMap.Segment<K,V>[])new java.util.concurrent.ConcurrentHashMap.Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }
复制代码

 

放入Segment对象的 put方法:
复制代码
   public V put(K key, V value) {

        java.util.concurrent.ConcurrentHashMap.Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        // 算出元素存到Segment数组中的那个位置   segmentMask 就是数组减一
        int j = (hash >>> segmentShift) & segmentMask;
//        获取 segments 数组中第j个元素
        if ((s = (java.util.concurrent.ConcurrentHashMap.Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
                (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            // 如果第j个位置为空  就生成一个Segement元素
            s = ensureSegment(j);
        // 操作Segment对象中的put方法
        return s.put(key, hash, value, false);
    }
复制代码

 

生成一个Segment对象,并放入到Segment数组的指定位置:
复制代码
 private java.util.concurrent.ConcurrentHashMap.Segment<K,V> ensureSegment(int k) {
        final java.util.concurrent.ConcurrentHashMap.Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        java.util.concurrent.ConcurrentHashMap.Segment<K,V> seg;
        // 这里条件的判断 和外面一样 为了避免其他线程已经在这个位置生成了一个Segment对象了 如果其他线程已经生成了就直接返回
        //
        if ((seg = (java.util.concurrent.ConcurrentHashMap.Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            // 这里获取ss[0]作为基础数据
            java.util.concurrent.ConcurrentHashMap.Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;  // 获取HashEntry数组大小
            float lf = proto.loadFactor;   // 加载因子
            int threshold = (int)(cap * lf);  // 计算阈值
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[] tab = (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[])new java.util.concurrent.ConcurrentHashMap.HashEntry[cap];
            // 这里又判断了一次Segment对象是否存在
            if ((seg = (java.util.concurrent.ConcurrentHashMap.Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                    == null) { // recheck
                // 如果走到这里还为空 才创建一个新的
                java.util.concurrent.ConcurrentHashMap.Segment<K,V> s = new java.util.concurrent.ConcurrentHashMap.Segment<K,V>(lf, threshold, tab);
                // 通过cas操作把新建的Segment放入 注意条件里面有 == null判断  保证只有一个线程能在这个位置放一个Segment对象
                while ((seg = (java.util.concurrent.ConcurrentHashMap.Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                        == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }
复制代码

 Segment中的put方法,把元素放到HashEntry数组中。

复制代码
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            // 加鎖  tryLock() 是非阻塞的(获取到锁立即返回true  获取不到锁 立即返回false)
            //  lock() 是阻塞的,获取到锁是会返回的  获取不到锁就阻塞住直到获取
            // 获取到了 返回一个null  如果获取不到 scanAndLockForPut  遍历数组/使用lock进行等待
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> node = tryLock() ? null :
                    scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                // 内部table
                java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> first = entryAt(tab, index);
                for (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                                (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            // 扩容  这个node是 当前HashEntry数组的头节点
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }
复制代码
复制代码
 private java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> first = entryForHash(this, hash);
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> e = first;
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            while (!tryLock()) {
                java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                        (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }
复制代码

 扩容,扩容只针对Segment内部的HashEntry数组:

复制代码
  @SuppressWarnings("unchecked")
        private void rehash(java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> node) {
            /*
             * Reclassify nodes in each list to new table.  Because we
             * are using power-of-two expansion, the elements from
             * each bin must either stay at same index, or move with a
             * power of two offset. We eliminate unnecessary node
             * creation by catching cases where old nodes can be
             * reused because their next fields won't change.
             * Statistically, at the default threshold, only about
             * one-sixth of them need cloning when a table
             * doubles. The nodes they replace will be garbage
             * collectable as soon as they are no longer referenced by
             * any reader thread that may be in the midst of
             * concurrently traversing table. Entry accesses use plain
             * array indexing because they are followed by volatile
             * table write.
             */
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1;
            threshold = (int)(newCapacity * loadFactor);
            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[] newTable =
                    (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>[]) new java.util.concurrent.ConcurrentHashMap.HashEntry[newCapacity];
            int sizeMask = newCapacity - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> e = oldTable[i];
                if (e != null) {
                    java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> next = e.next;
                    // 这里要注意,它是用的old 数组中的hash值与上新的数组长度减1 并没有rehash 和JDK1.7中的hashmap不一样
                    int idx = e.hash & sizeMask;
                    if (next == null)   //  Single node on list
                        newTable[idx] = e;
                    else { // Reuse consecutive sequence at same slot
                        java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> lastRun = e;
                        int lastIdx = idx;
                        // 这里的遍历是为了查找old数组 next后面节点是否有和其前面节点在新数组中是同一个索引位的情况
                        // 但是这里遍历完 只记录了最后一次出现这种情况
                        for (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        //  这里直接转移  可能这里直接转移了两个及以上的数据
                        newTable[lastIdx] = lastRun;
                        // Clone remaining nodes
                        for (java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                            V v = p.value;
                            int h = p.hash;
                            int k = h & sizeMask;
                            java.util.concurrent.ConcurrentHashMap.HashEntry<K,V> n = newTable[k];
                            newTable[k] = new java.util.concurrent.ConcurrentHashMap.HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            int nodeIndex = node.hash & sizeMask; // add the new node
            node.setNext(newTable[nodeIndex]);
            newTable[nodeIndex] = node;
            table = newTable;
        }
复制代码

 

JDK1.8  HashMap
复制代码
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
复制代码

 

复制代码
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        // 采用尾插法  JDK1.7中是头插法
                        p.next = newNode(hash, key, value, null);
                        // 上一步已经把新元素添加进去了  也就是说链表中已经有9个元素了
                        //  原来链表中有8个元素 就要转红黑树了  但是不一定真正要转  还要数组长度大于64
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 这个方法里面并不一定真正的树化  优先扩容 也可以减小链表的长度
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            // 这里的扩容  直接++size进行扩容操作  和JDK1.7 也不一样   这里没有了重新hash的操作了
            resize();
        afterNodeInsertion(evict);
        return null;
    }
复制代码

  红黑树中有6个元素会退化成链表。

JDK1.8中的ConcurrentHashMap
复制代码
 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) {
        // 这里要求了key和value不能为空  在JDK1.7中是可以为空的
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab = table;;) {
            java.util.concurrent.ConcurrentHashMap.Node<K,V> f; int n, i, fh;
            //为空 初始化数组  可以保证只有一个线程初始化成功  通过cas拿到标识的线程进行初始化  其他线程通过Thread.yield()放弃CPU
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //使用Unsafe中的方法直接获取主存中某个索引为的数据
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 如果某个索引位为空  通过cas操作设置新的节点
                if (casTabAt(tab, i, null,
                        new java.util.concurrent.ConcurrentHashMap.Node<K,V>(hash, key, value, null)))
                    // 添加成功就推出循环
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                // 如果节点的hash值等于了 MOVED -1  说明有其他线程在操作数组进行扩容  当前线程就帮助进行扩容
                tab = helpTransfer(tab, f);
            else {
                // 走到这里说明  数组的索引位已经有元素了  可以进行插入 但是是插入链表还是红黑树呢  而且还有并发的问题
                V oldVal = null;
                // 对头节点进行加锁
                synchronized (f) {
                    // 加锁再重新检查一下  头节点没有发生变化
                    if (tabAt(tab, i) == f) {
                        // hash值大于零  说明是个链表  直接往链表里插入值
                        if (fh >= 0) {
                            binCount = 1;  // 记录链表中有多少的节点了 后面根据它判断 是否进行树化
                            for (java.util.concurrent.ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                java.util.concurrent.ConcurrentHashMap.Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    // 采用尾插法
                                    pred.next = new java.util.concurrent.ConcurrentHashMap.Node<K,V>(hash, key,
                                            value, null);
                                    break;
                                }
                            }
                        }
                        // 走到这里说明是个红黑树  这个TreeBin只是作为数组中的节点使用  它内部维护了红黑树的Root节点
                        else if (f instanceof java.util.concurrent.ConcurrentHashMap.TreeBin) {
                            java.util.concurrent.ConcurrentHashMap.Node<K,V> p;
                            binCount = 2;
                            if ((p = ((java.util.concurrent.ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
                                    value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        // 大于阈值转换成红黑树
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        // 数组的长度加一  这里的加1 很复杂 为了让效率更好  使用一个BaseCount,CountCell数组 两个元素来进行统计数组长度,
        //  第一个线程想操作BaseCoount 加1
        addCount(1L, binCount);
        return null;
    }
复制代码

 

初始化的逻辑:
复制代码
/**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final java.util.concurrent.ConcurrentHashMap.Node<K,V>[] initTable() {
        java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {  // 注意这里使用的是循环
            if ((sc = sizeCtl) < 0)
                // 说明有其他线程正常初始化  当前线程让出时间片
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 看那个线程cas 设置sizeCtl =-1 成功  就进行初始化操作
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        java.util.concurrent.ConcurrentHashMap.Node<K,V>[] nt = (java.util.concurrent.ConcurrentHashMap.Node<K,V>[])new java.util.concurrent.ConcurrentHashMap.Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);  // 计算数组下次扩容的阈值 相当于  n*0.75
                    }
                } finally {
                    sizeCtl = sc; // 初始化结束  sizeCtl就是下次扩容时的阈值
                }
                break;
            }
        }
        return tab;
    }
复制代码

 

链表转红黑树:
复制代码
/**
     * Replaces all linked nodes in bin at given index unless table is
     * too small, in which case resizes instead.
     */
    private final void treeifyBin(java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab, int index) {
        java.util.concurrent.ConcurrentHashMap.Node<K,V> b; int n, sc;
        if (tab != null) {
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                tryPresize(n << 1);
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                synchronized (b) {
                    if (tabAt(tab, index) == b) {
                        java.util.concurrent.ConcurrentHashMap.TreeNode<K,V> hd = null, tl = null;
                        for (java.util.concurrent.ConcurrentHashMap.Node<K,V> e = b; e != null; e = e.next) {
                            java.util.concurrent.ConcurrentHashMap.TreeNode<K,V> p =
                                    new java.util.concurrent.ConcurrentHashMap.TreeNode<K,V>(e.hash, e.key, e.val,
                                            null, null);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        // 直接new了一个TreeBin  传进去hd 是红黑树的根节点  在TreeBin构造方法中构造红黑树  红黑树节点还是TreeNode
                        setTabAt(tab, index, new java.util.concurrent.ConcurrentHashMap.TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }
复制代码

 

数组的长度加1
复制代码
 /**
     * Adds to count, and if table is too small and not already
     * resizing, initiates transfer. If already resizing, helps
     * perform transfer if work is available.  Rechecks occupancy
     * after a transfer to see if another resize is already needed
     * because resizings are lagging additions.
     *
     * @param x the count to add
     * @param check if <0, don't check resize, if <= 1 only check if uncontended
     */
    private final void addCount(long x, int check) {
        java.util.concurrent.ConcurrentHashMap.CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
                //  counterCells 不为空   则操作counterCells 进行计数  如果为空  尝试一次在基数上进行加 1 如果失败 走下面的逻辑
                !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {

            java.util.concurrent.ConcurrentHashMap.CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                    // 每个线程生成一个随机数 ThreadLocalRandom.getProbe() 除非强制重置  否则不会变化
                    // 如果当前线程在 counterCells 中的位置为空 则走fullAddCount逻辑 如果不为空尝试在当前位置的元素属性value上加1 如果失败走 fullAddCount 逻辑
                    (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                    !(uncontended =
                            U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                // 主要操作 counterCells 进行初始化,扩容,加1 操作等   反正这个方法总能保证 加1成功的
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            // 统计下数组的长度 就是计算下BaseCount和 counterCells 数组中每个元素的value值的和
            s = sumCount();
        }
        if (check >= 0) {
            // sizeCtl 默认为0  -1 表示正在初始化  sizeCtl< -1 说明多个线程正在扩容  正常情况下等于数组阈值
            java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab, nt; int n, sc;
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                    (n = tab.length) < MAXIMUM_CAPACITY) {
                int rs = resizeStamp(n);
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                        (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }
复制代码

 

操作CounterCell 进行数组长度加1 
复制代码
    // See LongAdder version for explanation
    private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            java.util.concurrent.ConcurrentHashMap.CounterCell[] as; java.util.concurrent.ConcurrentHashMap.CounterCell a; int n; long v;
            if ((as = counterCells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {            // Try to attach new Cell
                        java.util.concurrent.ConcurrentHashMap.CounterCell r = new java.util.concurrent.ConcurrentHashMap.CounterCell(x); // Optimistic create
                        if (cellsBusy == 0 &&
                                U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            try {               // Recheck under lock
                                java.util.concurrent.ConcurrentHashMap.CounterCell[] rs; int m, j;
                                if ((rs = counterCells) != null &&
                                        (m = rs.length) > 0 &&
                                        rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                        U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                    try {
                        if (counterCells == as) {// Expand table unless stale
                            java.util.concurrent.ConcurrentHashMap.CounterCell[] rs = new java.util.concurrent.ConcurrentHashMap.CounterCell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            counterCells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = ThreadLocalRandom.advanceProbe(h);
            }
            else if (cellsBusy == 0 && counterCells == as &&
                    U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {                           // Initialize table
                    if (counterCells == as) {
                        java.util.concurrent.ConcurrentHashMap.CounterCell[] rs = new java.util.concurrent.ConcurrentHashMap.CounterCell[2];
                        rs[h & 1] = new java.util.concurrent.ConcurrentHashMap.CounterCell(x);
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          // Fall back on using base
        }
    }
复制代码

 

计算数组长度:
复制代码
  final long sumCount() {
        java.util.concurrent.ConcurrentHashMap.CounterCell[] as = counterCells; java.util.concurrent.ConcurrentHashMap.CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
复制代码

 

扩容:
对于一个线程来说,首先计算出一个步长,假设为2,它会从原数组的后面往前移动两位元素到新数组,如果同时还有另外一个线程来扩容,也会算一个步长假设 =1,这个线程就会把倒数第三个元素转移到新数组中,而且一次扩容完,会判断新数组的长度是否大于阈值,如果大于的
话,继续对新数组进行扩容。
复制代码
  /**
     * Adds to count, and if table is too small and not already
     * resizing, initiates transfer. If already resizing, helps
     * perform transfer if work is available.  Rechecks occupancy
     * after a transfer to see if another resize is already needed
     * because resizings are lagging additions.
     *
     * @param x the count to add
     * @param check if <0, don't check resize, if <= 1 only check if uncontended
     */
    private final void addCount(long x, int check) {
        java.util.concurrent.ConcurrentHashMap.CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
                //  counterCells 不为空   则操作counterCells 进行计数  如果为空  尝试一次在基数上进行加 1 如果失败 走下面的逻辑
                !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {

            java.util.concurrent.ConcurrentHashMap.CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                    // 每个线程生成一个随机数 ThreadLocalRandom.getProbe() 除非强制重置  否则不会变化
                    // 如果当前线程在 counterCells 中的位置为空 则走fullAddCount逻辑 如果不为空尝试在当前位置的元素属性value上加1 如果失败走 fullAddCount 逻辑
                    (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                    !(uncontended =
                            U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                // 主要操作 counterCells 进行初始化,扩容,加1 操作等   反正这个方法总能保证 加1成功的
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            // 统计下数组的长度 就是计算下BaseCount和 counterCells 数组中每个元素的value值的和
            s = sumCount();
        }
        if (check >= 0) {
            // sizeCtl 默认为0  -1 表示正在初始化  sizeCtl< -1 说明多个线程正在扩容  正常情况下等于数组阈值
            java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab, nt; int n, sc;
            // 扩容的逻辑
            // 数组的长度大于阈值
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                    (n = tab.length) < MAXIMUM_CAPACITY) {
                // 这里返回的就是一个绝对值很大的负数 后面要赋值给sizeCtl
                int rs = resizeStamp(n);
                // 如果小于零 说明  有其他线程已经走到transfer的逻辑了
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //  说明还没线程进行扩容  进行cas操作赋值sizeCtl为 负数 绝对值很大的负数
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                        (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }
复制代码
复制代码
/**
     * Moves and/or copies the nodes in each bin to new table. See
     * above for explanation.
     */
    private final void transfer(java.util.concurrent.ConcurrentHashMap.Node<K,V>[] tab, java.util.concurrent.ConcurrentHashMap.Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            // 最小步长 16
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                // 生成新的数组 扩容两倍长度
                java.util.concurrent.ConcurrentHashMap.Node<K,V>[] nt = (java.util.concurrent.ConcurrentHashMap.Node<K,V>[])new java.util.concurrent.ConcurrentHashMap.Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;  // 新数组
            transferIndex = n;     // 老数组长度
        }
        int nextn = nextTab.length;
        // 这个对象  其他put操作的线程如果操作的位置是fwd 对象  它的hash是  MOVED=-1  说明数组正在扩容  进行put操作的线程就来帮助进行扩容
        java.util.concurrent.ConcurrentHashMap.ForwardingNode<K,V> fwd = new java.util.concurrent.ConcurrentHashMap.ForwardingNode<K,V>(nextTab);
        boolean advance = true;  // 这个用来表示 当前这个线程转移完步长内的元素之后 还要不要继续往前走转移其他元素
        boolean finishing = false; // to ensure sweep before committing nextTab   // 为true的时候 表示当前这个线程的任务算是做完了 没有其他事情要去做了
        for (int i = 0, bound = 0;;) {
            java.util.concurrent.ConcurrentHashMap.Node<K,V> f; int fh;
            while (advance) {  // 这个循环 是为了计算出当前这个线程 本次循环需要处理的元素的范围
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                        (this, TRANSFERINDEX, nextIndex,
                                nextBound = (nextIndex > stride ?
                                        nextIndex - stride : 0))) {
                    bound = nextBound;  //
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        java.util.concurrent.ConcurrentHashMap.Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            java.util.concurrent.ConcurrentHashMap.Node<K,V> lastRun = f;
                            for (java.util.concurrent.ConcurrentHashMap.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 (java.util.concurrent.ConcurrentHashMap.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 java.util.concurrent.ConcurrentHashMap.Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new java.util.concurrent.ConcurrentHashMap.Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof java.util.concurrent.ConcurrentHashMap.TreeBin) {
                            java.util.concurrent.ConcurrentHashMap.TreeBin<K,V> t = (java.util.concurrent.ConcurrentHashMap.TreeBin<K,V>)f;
                            java.util.concurrent.ConcurrentHashMap.TreeNode<K,V> lo = null, loTail = null;
                            java.util.concurrent.ConcurrentHashMap.TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (java.util.concurrent.ConcurrentHashMap.Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                java.util.concurrent.ConcurrentHashMap.TreeNode<K,V> p = new java.util.concurrent.ConcurrentHashMap.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 java.util.concurrent.ConcurrentHashMap.TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                    (lc != 0) ? new java.util.concurrent.ConcurrentHashMap.TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }
复制代码

 

总结:

JDK8中的HashMap与JDK7的HashMap有什么不一样?

  1. JDK8中新增了红黑树,JDK8是通过数组+链表+红黑树来实现的
  2. JDK7中链表的插入是用的头插法,而JDK8中则改为了尾插法
  1. JDK8中的因为使用了红黑树保证了插入和查询了效率,所以实际上JDK8中的Hash算法实现的复杂度降低了
  2. JDK8中数组扩容的条件也发了变化,只会判断是否当前元素个数是否查过了阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值。
  1. JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容

 

HashMap中PUT方法的流程?

  1. 通过key计算出一个hashcode
  2. 通过hashcode与“与操作”计算出一个数组下标
  1. 在把put进来的key,value封装为一个entry对象
  2. 判断数组下标对应的位置,是不是空,如果是空则把entry直接存在该数组位置
  1. 如果该下标对应的位置不为空,则需要把entry插入到链表中
  2. 并且还需要判断该链表中是否存在相同的key,如果存在,则更新value
  1. 如果是JDK7,则使用头插法
  2. 如果是JDK8,则会遍历链表,并且在遍历链表的过程中,统计当前链表的元素个数,如果超过8个,则先把链表转变为红黑树,并且把元素插入到红黑树中

JDK8中链表转变为红黑树的条件?

  1. 链表中的元素的个数为8个或超过8个
  2. 同时,还要满足当前数组的长度大于或等于64才会把链表转变为红黑树。为什么?因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题。

 

HashMap扩容流程是怎样的?

  1. HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容
  2. 在HashMap中也是一样,先新建一个2被数组大小的数组
  1. 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去
  2. 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实现时是有不一样的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
  1. 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。
  2. 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。

 

 

为什么HashMap的数组的大小是2的幂次方数?

JDK7的HashMap是数组+链表实现的

JDK8的HashMap是数组+链表+红黑树实现的

 

当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个index不能越界。

 

在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过 hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,但是也有一个前提条件,就是数组的长度得是一个2的幂次方数。

 

为什么HashMap在多线程扩容时会出现循环链表的问题?

https://www.yuque.com/renyong-jmovm/kb/syatz1

 

ConcurrentHashMap和HashMap的区别是什么?

 

ConcurrentHashMap是HashMap的升级版,HashMap是线程不安全的,而ConcurrentHashMap是线程安全。而其他功能和实现原理和HashMap类似。

 

JDK8的ConcurrentHashMap和JDK7的ConcurrentHashMap有什么区别?

 

  1. JDK8中新增了红黑树
  2. JDK7中使用的是头插法,JDK8中使用的是尾插法
  1. JDK7中使用了分段锁,而JDK8中没有使用分段锁了
  2. JDK7中使用了ReentrantLock,JDK8中没有使用ReentrantLock了,而使用了Synchronized
  1. JDK7中的扩容是每个Segment内部进行扩容,不会影响其他Segment,而JDK8中的扩容和HashMap的扩容类似,只不过支持了多线程扩容,并且保证了线程安全

 

ConcurrentHashMap是如何保证并发安全的?

 

JDK7中ConcurrentHashMap是通过ReentrantLock+CAS+分段思想来保证的并发安全的,在JDK7的ConcurrentHashMap中,首先有一个Segment数组,存的是Segment对象,Segment相当于一个小HashMap,Segment内部有一个HashEntry的数组,也有扩容的阈值,同时Segment继承了ReentrantLock类,同时在Segment中还提供了put,get等方法,比如Segment的put方法在一开始就会去加锁,加到锁之后才会把key,value存到Segment中去,然后释放锁。

 

同时在ConcurrentHashMap的put方法中,会通过CAS的方式把一个Segment对象存到Segment数组的某个位置中。

 

同时因为一个Segment内部存在一个HashEntry数组,所以和HashMap对比来看,相当于分段了,每段里面是一个小的HashMap,每段公用一把锁,同时在ConcurrentHashMap的构造方法中是可以设置分段的数量的,叫做并发级别concurrencyLevel.

 

JDK8中ConcurrentHashMap是通过synchronized+cas来实现了。在JDK8中只有一个数组,就是Node数组,Node就是key,value,hashcode封装出来的对象,和HashMap中的Entry一样,在JDK8中通过对Node数组的某个index位置的元素进行同步,达到该index位置的并发安全。同时内部也利用了CAS对数组的某个位置进行并发安全的赋值。

 

JDK8中的ConcurrentHashMap为什么使用synchronized来进行加锁?

 

JDK8中使用synchronized加锁时,是对链表头结点和红黑树根结点来加锁的,而ConcurrentHashMap会保证,数组中某个位置的元素一定是链表的头结点或红黑树的根结点,所以JDK8中的ConcurrentHashMap在对某个桶进行并发安全控制时,只需要使用synchronized对当前那个位置的数组上的元素进行加锁即可,对于每个桶,只有获取到了第一个元素上的锁,才能操作这个桶,不管这个桶是一个链表还是红黑树。

 

想比于JDK7中使用ReentrantLock来加锁,因为JDK7中使用了分段锁,所以对于一个ConcurrentHashMap对象而言,分了几段就得有几个ReentrantLock对象,表示得有对应的几把锁。

 

而JDK8中使用synchronized关键字来加锁就会更节省内存,并且jdk也已经对synchronized的底层工作机制进行了优化,效率更好。

 

 

JDK7中的ConcurrentHashMap是如何扩容的?

 

JDK7中的ConcurrentHashMap和JDK7的HashMap的扩容是不太一样的,首先JDK7中也是支持多线程扩容的,原因是,JDK7中的ConcurrentHashMap分段了,每一段叫做Segment对象,每个Segment对象相当于一个HashMap,分段之后,对于ConcurrentHashMap而言,能同时支持多个线程进行操作,前提是这些操作的是不同的Segment,而ConcurrentHashMap中的扩容是仅限于本Segment,也就是对应的小型HashMap进行扩容,所以是可以多线程扩容的。

 

每个Segment内部的扩容逻辑和HashMap中一样。

 

JDK8中的ConcurrentHashMap是如何扩容的?

 

首先,JDK8中是支持多线程扩容的,JDK8中的ConcurrentHashMap不再是分段,或者可以理解为每个桶为一段,在需要扩容时,首先会生成一个双倍大小的数组,生成完数组后,线程就会开始转移元素,在扩容的过程中,如果有其他线程在put,那么这个put线程会帮助去进行元素的转移,虽然叫转移,但是其实是基于原数组上的Node信息去生成一个新的Node的,也就是原数组上的Node不会消失,因为在扩容的过程中,如果有其他线程在get也是可以的。

 

JDK8中的ConcurrentHashMap有一个CounterCell,你是如何理解的?

 

CounterCell是JDK8中用来统计ConcurrentHashMap中所有元素个数的,在统计ConcurentHashMap时,不能直接对ConcurrentHashMap对象进行加锁然后再去统计,因为这样会影响ConcurrentHashMap的put等操作的效率,在JDK8的实现中使用了CounterCell+baseCount来辅助进行统计,baseCount是ConcurrentHashMap中的一个属性,某个线程在调用ConcurrentHashMap对象的put操作时,会先通过CAS去修改baseCount的值,如果CAS修改成功,就计数成功,如果CAS修改失败,则会从CounterCell数组中随机选出一个CounterCell对象,然后利用CAS去修改CounterCell对象中的值,因为存在CounterCell数组,所以,当某个线程想要计数时,先尝试通过CAS去修改baseCount的值,如果没有修改成功,则从CounterCell数组中随机取出来一个CounterCell对象进行CAS计数,这样在计数时提高了效率。

 

所以ConcurrentHashMap在统计元素个数时,就是baseCount加上所有CountCeller中的value只,所得的和就是所有的元素个数。

 



























 

posted @   蒙恬括  阅读(16)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示