Multi-thread HashMap's endless loop problem, multi-thread hashmap
Multithreading[HashMap]Problems:
1. After a multi-threaded put operation, the get operation leads to an endless loop.
2. After multi-threaded put is not a NULL element, the get operation returns a NULL value.
3. multi-threaded put operations lead to element loss.
This article focuses on [HashMap]-endless loop.
Why is an endless loop?
As we all know, HashMap uses a linked list to solve Hash conflicts. For details about HashMap analysis, refer to the analysis of HashMap source code in Java Collection. Because it is a linked list structure, it is easy to form a closed link. In this way, as long as there is a thread to perform the get operation on this HashMap, an endless loop will occur. However, I'm curious about how this closed link is formed. In the case of a single thread, only one thread operates on the data structure of HashMap, and it is impossible to generate a closed loop. This happens only when multiple threads are concurrently executed. That is, if the size> initialCapacity * loadFactor is used during the put operation, the HashMap will perform the rehash operation, the structure of HashMap will change dramatically. It is very likely that the rehash operation is triggered in both threads at the same time, resulting in a closed loop.
Next we will analyze how this loop is generated step by step from the source code. Let's take a look at the put Operation:
Storage Data put
Public V put (K key, V value ){...... // calculate the Hash value int hash = hash (key. hashCode (); int I = indexFor (hash, table. length); // if the key has been inserted, replace the old value (link operation) 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 ++; // The key does not exist. Add a node addEntry (hash, key, value, I); return null ;}
When we put an element in a HashMap, we first calculate the hash value of the key to the position (subscript) of the element in the array ), then you can put this element in the corresponding position. If there are other elements in the position where the element is located, the elements in the same single position will be stored in the form of a linked list, and the newly added elements will be placed in the chain header, the previously added items are placed at the end of the chain.
Check whether capacity exceeds the limit addEntry
As you can see, if the size has exceeded threshold, you need to perform the resize operation to create a larger hash table, and then migrate the data from the old Hash table to the new Hash table:
Resize a Hash table
Void resize (int newCapacity) {Entry [] oldTable = table; int oldCapacity = oldTable. length ;...... // create a new Hash Table Entry [] newTable = New Entry [newCapacity]; // migrate data from the Old Hash Table to the new Hash Table transfer (newTable ); table = newTable; threshold = (int) (newCapacity * loadFactor );}
When the table [] array capacity is small, it is prone to Hash collisions. Therefore, the size and capacity of the Hash table are very important. In general, when there is data to be inserted in the Hash table container, it will check whether the capacity has exceeded the set thredhold. If the size of the Hash table is exceeded, the size of the Hash table needs to be increased. This process is called resize.
When multiple threads add new elements to the HashMap at the same time, multiple resize operations may have an infinite loop, because each resize operation needs to map the old data to the new hash table, the code in HashMap # transfer () method is as follows:
Void transfer (Entry [] newTable) {Entry [] src = table; int newCapacity = newTable. length; // the following code indicates: // extract an element from OldTable and put it in NewTable 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 );}}}
Some of the green code is the culprit that leads to a sudden increase in CUP usage when using hashmap in multiple threads, thus blocking multiple threads.