Analysis of Data Loss During Concurrent Resizing in JDK 1.8 HashMap
I'm not sure if my following statement is correct, and the timing of HashMap resizing is also wrong. I just want to know whether the data loss scenario during resizing in JDK 1.8 is correct.
1. Core Conclusion
JDK 1.8's HashMap resolved the circular linked list infinite loop issue (present in JDK 1.7 during concurrent resizing) by adopting the tail-insertion method. However, it did not resolve the data loss issue caused by concurrent resizing. HashMap remains thread-unsafe; when multiple threads trigger resizing simultaneously, it will still result in lost elements and data overwrites.
2. Prerequisites for the Scenario
The initial capacity of the HashMap's underlying
tablearray is 2.The linked list at array index 1 is:
3 -> 7 -> 5.There are two concurrent threads, Thread A and Thread B, executing the
resize()method simultaneously.JDK 1.8 Resizing Logic: Nodes are split into two sub-lists based on the high bit of their hash values. The low-bit list remains at the original index, while the high-bit list is migrated to the position of
original index + original capacity. Nodes are migrated using tail insertion.
3. Thread Scheduling Sequence Reproducing the Data Loss
Step 1: Both threads enter the resizing logic; Thread A is suspended midway.
Threads A and B synchronously read the old array and begin traversing the linked list at index 1 to migrate nodes. Thread A processes node e=3 and prefetches the reference to the next node as next=7. At this point, a CPU context switch occurs, and Thread A is suspended.
Step 2: Thread B completes the resizing and replaces the array.
Thread B runs without interruption, fully traversing the entire linked list 3->7->5 and migrating nodes based on hash splitting:
Element 5: The high bit of its hash is 0, so it remains at index 1 of the new array.
Elements 3 and 7: The high bit of their hash is 1, so they are migrated to index 3 of the new array, forming the linked list
3->7->null.
Thread B completes the migration of all nodes, assigns the newly resized array to the HashMap's global table variable, and the reference to the old array becomes invalid.
The current state of the latest table in memory is:
table[1] = 5table[3] = 3 -> 7 -> null
Step 3: Thread A resumes execution and loses data due to stale references.
After being woken up, Thread A still holds the reference to the pre-resize old linked list and continues its loop execution:
It first processes node
e=3and completes its migration.It reads the prefetched
next=7, assigns it toe, and continues processing node 7.It reads the successor pointer of node 7 (
7.next = null), assigningnulltonext.The loop condition evaluates
next == null, causing the linked list traversal to terminate immediately.
Step 4: Final Result — Element 5 is permanently lost.
Thread A only traversed nodes 3 and 7, completely failing to read node 5. Consequently, it performs no migration operation on element 5. After Thread A completes its migration and overwrites the array, element 5 no longer exists in the new array, resulting in data loss.