HashMap and interpret source ConcurrentHashMap

A, HashMap source interpretation

1, put operation

  • HashCode performed on the key hash, then the index is calculated;

  • If you do not crash directly into the bucket in the bucket

  • If a collision in the presence bucket in the form of a linked list

  • If collisions result in long chain length (greater than or equal TREEIFY_THRESHOLD), put the list is converted to red-black tree

  • If the node already exists on replacing old value (ensure the uniqueness of the key)

  • If the bucket is full (more than load factor * current capacity), it must resize.


public V put(K key, V value) {
    //对key的hashCode进行hash
    return putVal(hash(key), key, value, false, true);
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // table未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
     // 桶中已经存在元素
    else {
        Node<K,V> e; K k;
        // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
        // 将第一个元素赋值给e,用e来记录
            e = p;
        // hash值不相等,即key不相等;为红黑树结点
        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) {
                 // 在尾部插入新结点
                    p.next = newNode(hash, key, value, null);
                  // 结点数量达到阈值,转化为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                // 跳出循环
                    break;
                }
                // 判断链表中结点的key值与插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                // 相等,跳出循环
                    break;
                // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值与插入元素相等的结点
        if (e != null) { // existing mapping for key
       // 记录e的value
            V oldValue = e.value;
        // onlyIfAbsent为false或者旧值为null
            if (!onlyIfAbsent || oldValue == null)
              //用新值替换旧值
                e.value = value;
             // 访问后回调
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
 // 结构性修改
    ++modCount;
  // 实际大小大于阈值则扩容
    if (++size > threshold)
        resize();
 // 插入后回调
    afterNodeInsertion(evict);
    return null;
}
复制代码

2, get operation

  • a.bucket in the first node direct hit

  • b. If the conflict through key.equals (k) to find the corresponding entry

  • If a tree, the tree through the key.equals (k) to find the time complexity is o (logn);

  • If the list, then the list by key.equals (k lookup), time complexity is o (n);

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
 * Implements Map.get and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @return the node, or null if none
 */
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
         // 直接命中
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
         // 未命中
        if ((e = first.next) != null) {
            //在树中get
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
             // 在链表中get
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
复制代码

3, to achieve hash function

Get and put in the process, the index calculation, performs the hash operation hashCode firstly, and then calculate the hash value by a subscript further, as shown in FIG.

static final int hash(Object key) {
    int h;
     // ^ :按位异或
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    //其中n是数组的长度,即Map的数组部分初始化长度
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
复制代码

We can see that in hashmap you want to find an element, it is necessary to obtain the corresponding location in the array based on the hash value of the key. This is how to calculate the position of the hash algorithm.

As mentioned above, the data structure is hashmap binding arrays and linked list, of course, we hope to uniform distribution of the positions of these elements hashmap inside, as far as possible so that the number of elements in each position is only one. So when we find this position when using hash algorithm, you can immediately know the location of the corresponding elements of what we want, and not have to traverse the list. So, our first thought is to hashcode for array length modulo operation. As a result, the distribution of the elements is relatively uniform.

But the "mode" of operation consumption is quite large, you can not find a faster, consume less way? Let's look at JDK1.8 source code is how to do

It is simply:

* High 16 bit constant, high and low 16 bit of a 16 bit XOR made (hashCode is converted to a binary 32-bit, 16 bits and 16 bits high and low 16 bit of a 16 bit XOR done)

* (N · 1) & hash = -> obtained subscript

4, leads the list of zip law too deep, why not a binary search tree instead of selecting red-black tree? Why not always use red-black tree?

We chose a red-black tree is a binary search tree in order to solve the defects: a binary search tree in exceptional cases will become a linear structure (This is the same with the original use of the list structure, resulting in a deep level of problem), traverse Find will be very slow. And the red-black tree after inserting the new data may be required by the L, D, discoloration of these operations to maintain balance. The introduction of red-black tree is to find data quickly, to solve the problem list query depth. We know that red-black tree belongs to the balanced binary tree, in order to maintain "balance" is a price to pay, but the cost of the loss of resources is less than the traverse linear list. So when the length is greater than 8, it will use the red-black tree; if the chain length is very short, then, there is no need to introduce red-black tree, but will slow introduction.

5, insight into red-black tree

  • 1, i.e. each node in the non-red black

  • 2, the root is always black

  • 3. If a node is red, then its child nodes must be black (not necessarily vice versa)

  • 4, each leaf node are black empty node (NIL node)

  • 5, each path from the root node to a leaf node or a blank node, must contain the same number of black nodes (i.e., the same black height)

6, if the HashMap size exceeds the load factor (load factor) define the capacity of how to do?

The default size HashMap load factor is 0.75. That is, when a Map bucket filled 75 percent of the time, and, like other collections (such as ArrayList, etc.), will create an array of bucket twice the original size of the HashMap Map to re-adjust the size, and the original object placed in a new bucket array. This process is called rehashing.

7, re-adjust the size of the HashMap What problems do?

Re-adjust the size of the HashMap time, the existence of conditions of competition.

Because if two threads are found HashMap need to resize, and they will also try to adjust the size. In the resizing process, the order of elements stored in a linked list in turn. Since moving to a new location when the bucket, HashMap does not take an element on the end of the list, but on the head. This is to avoid traversing the tail (tail traversing). If the conditions of competition occurs, then the cycle of death. HashMap does not use a multi-threaded environment.

8, multiple threads can cause an infinite loop, which is how it happened?

HashMap capacity is limited. After several elements when inserted, so that the HashMap reaches a certain saturation, the probability of conflict Key mapping positions will gradually increase. At this time, HashMap need to expand its length, which is carried Resize.

  • Expansion: Creating a New Entry empty array, the length is 2 times the original array

  • rehash: Entry traverse the original array, all the re-Hash Entry to the new array

9, RESIZE implementation

When put, if we find the current bucket level of occupancy has exceeded Load Factor desired proportion, it will resize occur. In the course of the resize, simply means that the bucket expanded to 2 times, and then recalculate the index, the node and then put in a new bucket. When exceeding the limit will resize, but also because we are using twice extended power (referring to the length of the expansion of the original 2-fold), so the position of the element either in the original position, either moving again in the original position 2 to the power of position.

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
 // 超过最大值就不再扩充了,就只好随你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
  // 没超过最大值,就扩充为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
// 计算新的resize上限
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
 // 把每个bucket都移动到新的buckets中
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                         // 原索引
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                         // 原索引+oldCap
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                        // 原索引放到bucket里
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 原索引+oldCap放到bucket里
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
复制代码

resize process to modify the size of the Hash table array is used twice extended power (defined as 2 times the original length), so there are two advantages

  • 1, in the hashmap of source code. method calls put indexFor (int h, int length) method, which is mainly found in the location of this entry in the Hash table array based on the hash value of the key, the source code is as follows:
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
复制代码

上述代码也相当于对length求模。 注意最后return的是h&(length-1)。如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。 length-1 二进制中为1的位数越多,那么分布就平均。

  • 2、以下图为例,其中图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,n代表length。

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

resize过程中不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图(一方面位运算更快,另一方面抗碰撞的Hash函数其实挺耗时的):

二、ConcurrentHashMap源码解读

1、ConcurrentHashMap的key和value不能为null值

ConcurrentHashmap and Hashtable are concurrent, so that there will be a problem, when you get the corresponding value by get (k), if acquired is null, you can not judge, it is put (k, v) time value is null, or have never done this key mapping. HashMap non-concurrent, this can be done by Analyzing contains (key). And ConcurrentHashMap calling m.containsKey (key) and m.get (key), these two methods are not locked, calls may be other threads when m changed. If a thread m.containsKey (k) is true, when not execute m.get (k) of, k is another thread to delete, then m.get (k) returns null. If a null value is allowed, it would also be wrong judgment is k exist; thus it can not allow a null value shows a normal current k does not exist. So we should not have the following wording in ConcurrentHashMap, Key and Value does not allow null values. In fact, Value does not allow null values ​​can, Key appears to have little effect is null.

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}
复制代码

2, ConcurrentHashMap key attributes

ConcurrentHashMap implemented in 1.8, compared to 1.7 basically all changed out. Instead enabled a whole new way CAS algorithm to achieve, Node + CAS + Synchronized. Data structure and it follows the same period HashMap version of the idea, by the way is still the underlying array + + list red-black tree idea. For lock granularity, adjusted for each array element lock (Node), then the hash algorithm positioning node is simplified, so bring a malpractice Hash exacerbate conflict. Therefore, when the number of list node is greater than 8, the list will be stored as a red-black tree conversions. Thus, the time complexity of the query from the original will be O (n) becomes O (logN). Below is the basic structure:

Related attributes:

/**
*表初始化和调整控件大小。当为负时,

正在初始化或调整表的大小:初始化为-1,

否则-(1+活动调整大小线程的数目)。否则,

当表为空时,保留要在其上使用的初始表大小

创建,或默认为0。初始化后,保持

下一个要调整表大小的元素计数值
*/

private transient volatile int sizeCtl;
复制代码

sizeCtl for Table [] and expansion initialization operation, the state representative of different values ​​are as follows:

  • -1: table [] is initialized.
  • -N: N-1 expressed threads expansion operation in progress.

Non-negative situation:

  • If the table [] is not initialized, then the size table needs to be initialized.
  • If the initialization is completed, then the Table [] expansion threshold, default table [] 0.75 times the amount.

private static finalint DEFAULT_CONCURRENCY_LEVEL = 16;

  • DEFAULT_CONCURRENCY_LEVEL: indicates the default concurrency level, which is the table [] of default size.

private static final float LOAD_FACTOR = 0.75f;

  • LOAD_FACTOR: default load factor

static final int TREEIFY_THRESHOLD = 8;

  • TREEIFY_THRESHOLD: red-black tree list transfer threshold, when the table [i] is greater than the length of the following list 8 is converted into a red-black tree.

static final int UNTREEIFY_THRESHOLD = 6;

  • UNTREEIFY_THRESHOLD: red-black tree forwarding the list threshold, into the list (time expansion) when the chain length of <= 6.

Constructor:

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);
        this.sizeCtl = cap;
    }
复制代码

The code can be seen from the above, at the time of ConcurrentHashMap creating, and no initialization Table [] array, only the capacity of the Map, and so do the concurrency level assignment. Relevant node:

  • Node: This class is used to construct table [], the node read-only (no modification method).
  • TreeBin: red-black tree structure.
  • TreeNode: red-black tree node.
  • ForwardingNode: Temporary node (when using expansion).

table

transient volatile Node<K,V>[] table; Node Loads array as data containers of ConcurrentHashMap, by way of lazy loading, until the first time will be inserted in the data initialization, the size of the array is always a power of 2.

nextTable

private transient volatile Node<K,V>[] nextTable;volatile Node <K, V> [] nextTable; // use expansion, usually is null, and only when the expansion of non-null

sizeCtl

private transient volatile int sizeCtl;This attribute table is used to control the size of the array, whether or not according to whether initialization is being expansion and there are several situations: when the value is negative: If initialization is represented by -1, if n is -N then the current thread has N-1 for expansion operation; when the value is positive: if the current array expressed table is null, then the initialization process, sizeCtl expressed as a need for new length of the array; if already initialized, representing the current data container (table array) available capacity may be understood to threshold (insert nodes exceeds the threshold requires expansion), specifically refers to the length of the array is multiplied by the load factor n loadFactor; when the value is 0, i.e., a default initial value for the length of the array.

sun.misc.Unsafe U

private static final sun.misc.Unsafe U;在ConcurrentHashMapde的实现中可以看到大量的U.compareAndSwapXXXX的方法去修改ConcurrentHashMap的一些属性。这些方法实际上是利用了CAS算法保证了线程安全性,这是一种乐观策略,假设每一次操作都不会产生冲突,当且仅当冲突发生的时候再去尝试。而CAS操作依赖于现代处理器指令集,通过底层CMPXCHG指令实现。CAS(V,O,N)核心思想为:若当前变量实际值V与期望的旧值O相同,则表明该变量没被其他线程进行修改,因此可以安全的将新值N赋值给变量;若当前变量实际值V与期望的旧值O不相同,则表明该变量已经被其他线程做了处理,此时将新值N赋给变量操作就是不安全的,在进行重试。而在大量的同步组件和并发容器的实现中使用CAS是通过sun.misc.Unsafe类实现的,该类提供了一些可以直接操控内存和线程的底层操作,可以理解为java中的“指针”。该成员变量的获取是在静态代码块中:

static {
     try {
         U = sun.misc.Unsafe.getUnsafe();
         .......
     } catch (Exception e) {
         throw new Error(e);
     }
 }
复制代码

3、ConcurrentHashMap中关键内部类

Node

Node类实现了Map.Entry接口,主要存放key-value对,并且具有next域

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.
         */
        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;
        }
    }
复制代码

另外可以看出很多属性都是用volatile进行修饰的,也就是为了保证内存可见性。

TreeNode

树节点,继承于承载数据的Node类。而红黑树的操作是针对TreeBin类的,从该类的注释也可以看出,也就是TreeBin会将TreeNode进行再一次封装

   static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

        TreeNode(int hash, K key, V val, Node<K,V> next,
                 TreeNode<K,V> parent) {
            super(hash, key, val, next);
            this.parent = parent;
        }

        Node<K,V> find(int h, Object k) {
            return findTreeNode(h, k, null);
        }

        /**
         * Returns the TreeNode (or null if not found) for the given key
         * starting at given root.
         */
        final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
            if (k != null) {
                TreeNode<K,V> p = this;
                do  {
                    int ph, dir; K pk; TreeNode<K,V> q;
                    TreeNode<K,V> pl = p.left, pr = p.right;
                    if ((ph = p.hash) > h)
                        p = pl;
                    else if (ph < h)
                        p = pr;
                    else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                        return p;
                    else if (pl == null)
                        p = pr;
                    else if (pr == null)
                        p = pl;
                    else if ((kc != null ||
                              (kc = comparableClassFor(k)) != null) &&
                             (dir = compareComparables(kc, k, pk)) != 0)
                        p = (dir < 0) ? pl : pr;
                    else if ((q = pr.findTreeNode(h, k, kc)) != null)
                        return q;
                    else
                        p = pl;
                } while (p != null);
            }
            return null;
        }
    }
复制代码

TreeBin

这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象。

static final class TreeBin<K,V> extends Node<K,V> {
         TreeNode<K,V> root;
         volatile TreeNode<K,V> first;
         volatile Thread waiter;
         volatile int lockState;
         // values for lockState
         static final int WRITER = 1; // set while holding write lock
         static final int WAITER = 2; // set when waiting for write lock
         static final int READER = 4; // increment value for setting read lock
         ......
 }
复制代码

ForwardingNode

在扩容时才会出现的特殊节点,其key,value,hash全部为null。并拥有nextTable指针引用新的table数组。

static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        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;
                }
            }
        }
    }
复制代码

4、CAS关键操作

We mentioned above will be extensive use of CAS modify its properties and some operations in ConcurrentHashMap. Therefore, before understanding ConcurrentHashMap way we need to understand the following several commonly used algorithms utilize CAS to protect thread-safe operations.

Tabas

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);
 }
复制代码

The method used to obtain the array index table Node element i.

casTabAt

 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);
 }
复制代码

CAS operation setting table using the index in the array element i

setTabAt

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
     U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
 }
复制代码

The method used to set the table for the indexed array element i

5, focusing on methods to explain

put operation:

 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) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
            //若table未创建,则初始化
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //table[i]后面无节点时,直接创建Node(无锁操作)
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 如果当前正在扩容,则帮助扩容并返回最新table[]
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
            //在链表或者红黑树中追加节点
                V oldVal = null;
                //这里并没有使用ReentrantLock,说明synchronized已经足够优化了(1.7使用的ReentrantLock)
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                    //如果为链表结构
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //找到key,替换value
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                //在尾部插入Node
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //如果为红黑树
                        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) {
                //到达阀值,变为红黑树结构
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

复制代码

As seen from the above code, put the step as follows:

  • Parameter calibration.
  • If the table [] is not created, then initialized.
  • When the table [i] containing no node, the Node created directly (no lock operation).
  • If you are currently expansion, the expansion and help return the latest table [].
  • Then additional nodes in the linked list, or red-black tree.
  • Finally back reaches a threshold is determined, such as changes to reach the red-black tree structure.

get operations:

 public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //定位到table[]中的i
        int h = spread(key.hashCode());
        //若table[i]存在
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //比较链表头部
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //若为红黑树,查找树
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
                //循环链表查找
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
复制代码

Get method is relatively simple process that the code can be seen from the above steps:

  • First locate the Table [] is i.
  • If the table [i] exists, continue searching.
  • First compare the list head, if it is returned.
  • If it is then red-black tree, look for a tree.
  • Finally, look for recycling chain.

ConcurrentHashMap operation of the above does not get locked. So in the process of multi-threaded operation, it does not fully guarantee consistency. Here and 1.7 among similar, is a manifestation of weak consistency.

size operation:

  public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }


 public long mappingCount() {
        long n = sumCount();
        return (n < 0L) ? 0L : n; // ignore transient negative values
    }

 final long sumCount() {
        CounterCell[] as = counterCells; 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;
    }
复制代码

In JDK1.8 added a mappingCount () of the API. The API and size () return value is different is that Long type, so it is not Integer.MAX_VALUE size limits.

Two methods are called simultaneously, sumCount () method. Has a corresponding CounterCell for each table [i], after the above process did return the summation. Thus be seen, size () and mappingCount () Returns a is an estimated value. (This is different inside and implementation JDK1.7, which uses the 1.7 locked manner. This can also be seen inside JDK1.8 the expense of accuracy, in exchange for greater efficiency.)

Reproduced in: https: //juejin.im/post/5d09e2925188256683677de8

Guess you like

Origin blog.csdn.net/weixin_34163741/article/details/93166280