Briefly summarize the dead cycle of jdk1.7 HashMap expansion and jdk1.8 optimization

There are many online blogs about the dead cycle of jdk1.7 HashMap expansion, but many are posted with a large number of codes and diagrams. Here is only a brief summary. You still need to see the source code for details

Jdk1.7 causes of HashMap dead cycle

First paste the code (this is necessary)

    void resize(int newCapacity) {
		....
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
		...
    }
    
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next; //1. Get the next node of the current node first
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//2. Get the subscript of the new array after capacity expansion
                e.next = newTable[i];  //3. Point the next node of the current node to the head of the linked list of new coordinates
                newTable[i] = e; //4. Replace the new coordinate, i.e. the chain header, with the current node
                e = next; //5. Put the next node into circulation
            }
        }
    }

We know that HashMap is an array + linked list. Suppose there is a HashMap here, and hash conflicts are located at the position of subscript 3 to form a-b-c linked list

Capacity expansion steps

Simply combine the above transfer method, It can be seen that five steps have been taken:
1. Traversal linked list, here e namely a, next namely b
2. Get the new subscript after capacity expansion, hypothesis[3]above abc The new subscript for is 7,newTable[7]
3. hold a of next point newTable[7] Linked list header, Here is null,  a → null
4. hold newTable[7] Assign as a
5. take next, Namely b As the next cycle

  
Second cycle:
1. e namely b, next namely c
2. New subscript,  newTable[7]
3. b of next point newTable[7] Linked list header, Namely a. become b → a → null
4. newTable[7] The head of the linked list becomes b
5. c Put into next cycle

That is, although the traversal is from the head of the linked list to the back, each time the position is placed in the head of the new linked list, and finally reversed
Finally, it becomes c → b → a

Dead cycle

After knowing that the expansion is the reverse of the linked list, it is assumed that two threads trigger the expansion operation at the same time

Thread 2 runs to step 1, i.e
Entry<K,V> next = e.next;
Get e = a, next = b

At this time, thread 1 runs and the capacity expansion is completed. The new linked list newTable[7] is c → b → a

Thread 2 continues to execute, and it will also open up a new expanded array. Continue with the above steps

  1. Point the next of a to the head of the linked list of newTable[7], where is null, a → null
  2. Assign newTable[7] to a
  3. Take next, i.e. b, as the next loop

Now the situation is as shown in the figure

But when it comes to the next cycle,
e is B, but next obtains the new order after the capacity expansion of thread 1, obtains a, and continues to execute

  1. b's next points to the linked list header of newTable[7], that is, A. becomes b → a → null
  2. The linked list header of newTable[7] becomes b
  3. a put into the next cycle

In the next loop, e is a and next is null

  1. Point the next of a to the head of the linked list of newTable[7], here is b, a nd a → b → A is this step to form a closed loop
  2. Assign newTable[7] to a
  3. Take next, i.e. null, as the next loop, but use null as a condition to judge that the loop will not continue

The end result is this

summary

Causes of dead cycle:

  1. jdk1.7 is the head insertion method, that is, traverse the old linked list and insert the head of the new linked list, resulting in the reverse order
  2. Under multithreading, one thread reverses the order, and the other thread does not know it. The two nodes in the original order are maintained in the loop, and a closed-loop direction will be added during header insertion
  3. In the get query, there is an endless loop when traversing the linked list

Improvement of jdk1.8

code

 1 final Node<K,V>[] resize() {
 2    .....ellipsis
31     @SuppressWarnings({"rawtypes","unchecked"})
32         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
33     table = newTab;
34     if (oldTab != null) {
35         // Traverse and migrate each subscript of the old array
36         for (int j = 0; j < oldCap; ++j) {
37             Node<K,V> e;
38             if ((e = oldTab[j]) != null) {
39                 oldTab[j] = null;
40                 if (e.next == null) //If there is only one element in this position, put it directly in the new coordinate
41                     newTab[e.hash & (newCap - 1)] = e;
42                 else if (e instanceof TreeNode) //If it's a red black tree
43                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
44                 else { // If it's a linked list
45                     Node<K,V> loHead = null, loTail = null;
46                     Node<K,V> hiHead = null, hiTail = null;
47                     Node<K,V> next;
48                     do {
49                         next = e.next;
50                         // Here is to judge whether the highest bit is 0. If it is 0, it means that the expansion has no impact on it (because the binary of length-1 is all 1, and the expansion of 2 times only changes the highest bit by 1)
51                         if ((e.hash & oldCap) == 0) {
52                             if (loTail == null)
53                                 loHead = e;
54                             else
55                                 loTail.next = e;
56                             loTail = e;
57                         }
58                         // This indicates that it has been affected. New index = original index + oldCap
59                         else {
60                             if (hiTail == null)
61                                 hiHead = e;
62                             else
									//Key add element from tail
63                                 hiTail.next = e;
64                             hiTail = e;
65                         }
66                     } while ((e = next) != null);
67                     // Put the original index into the bucket
68                     if (loTail != null) {
69                         loTail.next = null;
70                         newTab[j] = loHead;
71                     }
72                     // Put the original index + oldCap into the bucket
73                     if (hiTail != null) {
74                         hiTail.next = null;
75                         newTab[j + oldCap] = hiHead;
76                     }
77                 }
78             }
79         }
80     }
81     return newTab;
82 }

It can be seen from line 63 that jdk1.8 adds elements from the tail. That is, the order of the linked list of the new subscripts is the same as that of the old array subscripts. Therefore, there will be no closed-loop pointing due to the reverse order

Problems in jdk1.8

The HashMap in jdk1.8 is still thread unsafe. Although elements are added from the tail during capacity expansion, if two threads operate at the same time, the capacity expansion of one thread may cause the new linked list to be overwritten

Secondly, jdk1.8 also has the problem of dead circulation

  • jdk8 may loop when PutTreeValue is used. The loop may loop in line 1816 or 2229 of hashMap, java version "1.8.0_111"
  • jstack found that it may be stuck in at java.util.HashMap$TreeNode.balanceInsertion(HashMap.java:2229)
  • It may also be stuck in at java.util.HashMap$TreeNode.root(HashMap.java:1816)

The reason is that the properties are modified because multiple threads operate on the same object

For example, here, if the parent s of two node s are each other, an endless loop is formed
Practice has proved that HashMap in JDK8 will still be dead cycle

        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;   //Line 1816
            }
        }


Tags: Java linked list source code

Posted on Tue, 26 Oct 2021 23:30:59 -0400 by my_r31_baby