It has always been known that hashmap is thread unsafe, but in the end, why is the hashmap thread unsafe? Under what circumstances may the problem occur when multithreading is concurrent?
The bottom layer of hashmap is an entry array. When a hash conflict occurs, hashmap uses a linked list to solve the problem. It stores the head node of the linked list at the corresponding array location. For a linked list, new nodes are added from the original node.
The description of hashmap in javadoc is as follows:
This implementation is not synchronous.If multiple threads access a hash ing at the same time, and at least one thread modifies the ing from the structureRequiredMaintain external synchronization. (Schema Modification refers to any operation to add or delete one or more mappings. Changing only the values associated with the key already included in the instance is not a schema modification .) This is generally done by synchronizing the objects that encapsulate the ing. If such an object does not exist, useCollections.synchronizedMapMethod To "Wrap" The ing. It is best to complete this operation at creation to prevent unexpected non-synchronous access to the ing, as shown below:
Map m = Collections.synchronizedMap(new HashMap(...));
1,
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); }
The above method is called when hashmap performs the put operation. Now, if both thread a and thread B call addentry for the same Array location, the two threads will get the current header node at the same time, and after a writes the new header node, B Also writes data to the new header node, so the write operation of B will overwrite the write operation of A, resulting in the loss of the write operation of.
2,
final Entry<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
The code for deleting a key-value pair is as follows:
When multiple threads operate on the same Array position at the same time, they will first obtain the header node stored in this position in the current state, and then perform computing operations respectively, then write the result to the location of the array. In fact, when writing back, other threads may have modified the location and will overwrite the modifications of other threads.
3. When a new key-value pair is added to addentry and the total number of key-value pairs exceeds the threshold, a resize operation is called. The Code is as follows:
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); }
This operation will generate a new capacity array, re-calculate and write all the key-value pairs of the original array, and then point to the new array.
When multiple threads detect that the total number of threads exceeds the threshold value, the resize operation is called at the same time to generate new arrays, rehash them, and assign them to the underlying array table of the map, in the end, only the new array generated by the last thread is assigned to the table variable. All other threads will be lost. In addition, when some threads have completed the assignment and other threads are at the beginning, the table that has been assigned the assignment will be used as the original array. This will also cause problems.