1. Multithreaded PUT can cause element loss

public class ConcurrentIssueDemo1 {

    private static Map<String, String> map = new HashMap<>();

    public static void main(String[] args) {
        Thread 1 => t1
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                for (int i = 0; i < 99999999; i++) {
                    map.put("thread1_key" + i, "thread1_value" + i);
                }
            }
        }).start();
        Thread 2 => t2
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                for (int i = 0; i < 99999999; i++) {
                    map.put("thread2_key" + i, "thread2_value"+ i); } } }).start(); }}Copy the code

Note ⚠️ : the above code may be thread unsafe, not directly run the thread unsafe situation.

Put source code analysis

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
  			/ / initialization
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            // Hash the TAB position and assign the element at that position to p. If this position is empty, a new node is placed at that position
            tab[i] = newNode(hash, key, value, null);
        else {
          // Append the list to the element that already exists in the TAB array index
            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) {
                  // Create a new node and append it to the list
                    if ((e = p.next) == null) {/ / # 1
                        p.next = newNode(hash, key, value, null);/ / # 2
                        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)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
Copy the code

When two threads execute the put value and hash it to the same tab-array location, thread 1 executes #2 and overwrites thread 1’s put value after thread 2 executes #2.

2. If PUT and GET are concurrent, GET may be null

Scenario: Thread 1 performs PUT and resize because the number of elements exceeds threshold. Thread 2 performs GET, which may cause this problem

/**
	* Initializes or doubles table size.  If null, allocates in
  * accord with initial capacity target held in field threshold.
 	* Otherwise, 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 in the new table.
  *
  * @return the table
*/
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;
            }
            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);
        }
        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>[])newNode[newCap]; #1table = newTab; #2
        if(oldTab ! =null) {
            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;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                elsehiTail.next = e; hiTail = e; }}while((e = next) ! =null);
                        if(loTail ! =null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if(hiTail ! =null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
Copy the code

When thread 1 executes to code #1, a new hash table is created with the newly calculated capacity, and #2 assigns the newly created empty hash table to the instance variable table. The table is empty. If thread 2 executes get at this point, it will get null.

3. In JDK7, HashMap concurrent put can create a loop linked list, resulting in an infinite loop of GET

Circular linked list the reason is that in the JDK1.7 expansion when the element from the old list to list when using the first method, lead to inconsistent elements before and after the expansion of the relative position, and add capacity may lead to the emergence of circular linked list in JDK1.8 fixes the problem, 1.8 use tail interpolation to the element with old chain table moved to the new list.

void resize(int newCapacity) {   // Pass in a new capacity
    Entry[] oldTable = table;    // Reference the Entry array before expansion
    int oldCapacity = oldTable.length;  
    if (oldCapacity == MAXIMUM_CAPACITY) {  // The size of the array before the expansion has reached the maximum (2^30)
        threshold = Integer.MAX_VALUE; // Change the threshold to the maximum value of int (2^31-1) so that the capacity will not be expanded later
        return;  
    }  

    Entry[] newTable = new Entry[newCapacity];  // Initialize a new Entry array
    transfer(newTable);                         / /!!!!! Move the data to the new Entry array, which contains the most important repositioning
    table = newTable;                           // The table property of the HashMap references the new Entry array
    threshold = (int) (newCapacity * loadFactor);// Change the threshold
}
/** * Transfers all entries from current table to newTable. */
// The key is the transfer method, which rehashes elements from the old hash table to the new hash table
void transfer(Entry[] newTable, boolean rehash) {
   Entry[] src = table;                   // SRC references the old Entry array
    int newCapacity = newTable.length;  
    for (int j = 0; j < src.length; j++) { // Iterate over the old Entry array
        Entry<K, V> e = src[j];             // Get each element of the old Entry array
        if(e ! =null) {  
            src[j] = null;// Release the object reference of the old Entry array (after the for loop, the old Entry array no longer references any objects)
            do {  
                Entry<K, V> next = e.next; / / # 1
                int i = indexFor(e.hash, newCapacity); / / # 2!!!!! Recalculates the position of each element in the array
                e.next = newTable[i]; / / tag [1]
                newTable[i] = e;      // Put the element on the array
                e = next;             // Access the element in the next Entry chain
            } while(e ! =null); }}}Copy the code

JDK1.7 formation of circular linked lists

When two threads execute simultaneously expanding logic, when performing to the # 1 statement, thread 2 CPU time slice to thread 1, thread 1 logic, expansion and performed to the form of figure 2, and then thread 2 continue # 2 statements, end of a cycle, to reach the state of figure 3, changing the pointer to a state of the right key 👉 pointer, perform a cycle again, It reaches the state shown in Figure 4, and finally performs a loop to reach the state shown in Figure 5, where the circular linked list is formed.

Get causes an infinite loop because of the loop list

 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) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k))))
                        return e;
                } while((e = e.next) ! =null);// The loop condition is always satisfied}}return null;
    }
Copy the code