Diagram of circular linked list of HashMap

Diagram of circular linked list of HashMap

@author: Jingdai
@date: 2021.03.20

Reviewing the knowledge points of HashMap, I always see that there may be a circular linked list problem in multi-threaded operations before jdk1.7. I don't understand it very well, so I studied the source code and drew the picture and finally figured it out and recorded it.

Since my computer only has jdk1.5 and jdk1.8, the following analysis is based on jdk1.5, and jdk1.7 should be the same.

Where the circular linked list happens

The circular linked list occurs when multiple threads simultaneously put the HashMap to expand the HashMap. Look at the code below.

public V put(K key, V value)

public V put(K key, V value) {
    
    
	if (key == null)
	    return putForNullKey(value);
        
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (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;
}

The preceding code is used to determine the insertion cycle of Entrywhether existing key, is to be replaced, this is not our concern, adding elements mainly in the second row of the inverse method addEntry. Look at this method below.

void addEntry(int hash, K key, V value, int bucketIndex)

void addEntry(int hash, K key, V value, int bucketIndex) {
    
    
	Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

See the last two lines of the code. If the added element reaches the expansion threshold, perform the resize operation, pass in a new array length of twice the capacity, and continue to enter this method.

void resize(int newCapacity)

void resize(int newCapacity) {
    
    
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
    
    
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}

Whether the first few lines of judgment oldCapacity code is already the largest, and the case is not expansion, see below where Entry[] newTable = new Entry[newCapacity];this line of code, when satisfied expansion conditions, creates a size for the new HashMap 2 multiples of length, and then call transfer method The old elements are passed to the new HashMap, and finally the new HashMap is passed to the table variable. The main expansion operation is in the transfer method, which is also the place where the circular linked list occurs. Let's look at it next.

void transfer(Entry[] newTable)

void transfer(Entry[] newTable) {
    
    
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
    
    
        Entry<K,V> e = src[j];
        if (e != null) {
    
    
            src[j] = null;
            do {
    
    
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);  
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

The code is not long, we have found the place where the circular linked list occurs, and we specifically analyze how to generate the circular linked list.

Circular linked list generation analysis

To understand how the circular linked list is generated, we first look at how the transfer method works normally in a single thread, and the following graphical analysis is used.

Insert picture description here

As shown in the figure, for simplicity, here we assume that the original array size of HashMap is 2, and the size after expansion is 4. In practice, this situation does not happen, but the principle is the same. The left of the picture is the original HashMap, and the right of the picture is the new HashMap. We only focus on the bucket with subscript 1 and don't care about the others. At the same time, it is assumed that elements 1, 2, and 3 will be inserted into the new HashMap's bucket 1. The following code analysis line by line.

Insert picture description here

Before entering the loop, execution first Entry<K,V> e = src[j];, then epoints to element 1, and then src[j] = null;, again nextpointing to ethe next element, i.e. element 2. Below that line int i = indexFor(e.hash, newCapacity);is calculated according to hash bucket insert subscript, we assume that works out to 1, that is inserted into a new No. 1 bucket, back I do not analyze this line of code. At the same time, because src[j] = nullof these three elements, the original HashMap is no longer used, and the following pictures are not drawn (drawing is very tiring ^ _ ^).

Insert picture description here

Then e.nextpoint newTable[i], Note that newTable[i]there is no further elements, it is null, even if the e.nextpoint null, then newTable[i] = e;, will be the new HashMap tub point No. 1 e, then the element 1 has been inserted in the new HashMap, then look.

Insert picture description here

Above, followed by the eand nextupdate it.

Insert picture description here

As shown in the figure above, element 2 will then be inserted into the list, and then e and next will be updated to insert element 3 into it. The steps for inserting element 2 and element 3 are the same, so I won't draw it. The updated and final figure is as follows.

Insert picture description here

Well, you can analyze the circular linked list after you understand how to expand the elements in a single thread. It should also be noted that when inserting elements, the head interpolation method is used, so the order of the elements in the old HashMap is 123, but in the new HashMap it becomes 321.

Continue multi-threaded, assuming that two threads must insert an element, and are entered into the implementation of the resize method, namely the method of execution to addEntry if (size++ >= threshold)when all judgment is true, then create a new one in its own thread newTable, and then proceeds to transferMethod. Still the above example, assuming that two threads are executed together, the initial look is as shown in the figure below.

Insert picture description here

We use e1and e2represent the local variables ein thread 1 and thread 2 respectively , and use next1and next2represent the local variables in thread 1 and thread 2 respectively next.

Insert picture description here

As shown in the figure above, suppose that when thread 1 and thread 2 execute to this step, thread 2 loses the time slice, and thread 1 continues to execute until the end, then after thread 1 ends, it becomes the following.

Insert picture description here

Then thread 2 obtains the time slice, starts execution, and then analyzes it step by step.

Insert picture description here

Insert picture description here

Insert picture description here

Insert picture description here

The above figure is to go step by step according to the code, go to the last step eis null, and withdrew from circulation, then have a ring, corresponding to the thread 2, which corresponds to this figure is below.

Insert picture description here

If Thread 2 will newTablewrite back table, ring arises, also found elements 3 also lost, nowhere to be found.

The above is an example of HashMap generating ring before jdk1.7 version, I hope it will be helpful to everyone. If there are any errors in the above, please correct me and learn and make progress together.

Guess you like

Origin blog.csdn.net/qq_41512783/article/details/115028747