Fundamentals 9 min read

Why HashMap Is Not Thread‑Safe in JDK 1.7 and JDK 1.8: Dead‑Loop and Data‑Loss Analysis

This article explains the thread‑safety problems of Java's HashMap in JDK 1.7 and JDK 1.8, showing how concurrent resize operations can create circular linked lists, cause infinite loops or data loss, and why the underlying implementation remains unsafe despite JDK 1.8's optimizations.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why HashMap Is Not Thread‑Safe in JDK 1.7 and JDK 1.8: Dead‑Loop and Data‑Loss Analysis

HashMap is known to be unsafe in multithreaded environments, but the exact reasons are often unclear; this article dissects the root causes in JDK 1.7 and JDK 1.8.

JDK 1.7 issue : during resize, the transfer method moves entries using head‑insertion, which reverses the linked‑list order. When two threads concurrently resize, one thread may be paused mid‑transfer while the other completes the resize, leading to a situation where the new table contains entries that form a cycle. This circular list causes infinite loops and, in some cases, array‑index‑out‑of‑bounds errors.

public class HashMapTest {
    public static void main(String[] args) {
        HashMapThread thread0 = new HashMapThread();
        HashMapThread thread1 = new HashMapThread();
        HashMapThread thread2 = new HashMapThread();
        HashMapThread thread3 = new HashMapThread();
        HashMapThread thread4 = new HashMapThread();
        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

class HashMapThread extends Thread {
    private static AtomicInteger ai = new AtomicInteger();
    private static Map
map = new HashMap<>();
    @Override
    public void run() {
        while (ai.get() < 1000000) {
            map.put(ai.get(), ai.get());
            ai.incrementAndGet();
        }
    }
}

The transfer method (excerpt) illustrates the problematic loop:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry
e : table) {
        while (null != e) {
            Entry
next = e.next;
            if (rehash) {
                e.hash = (null == e.key) ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

When thread A is paused after moving an entry but before updating the next pointer, and thread B finishes the resize, the shared memory may contain contradictory links (e.g., 7.next = 3 and 3.next = 7 ), forming a loop that later traversals cannot exit.

JDK 1.8 improvement : the resize algorithm switches to tail‑insertion, preventing circular lists, and introduces treeification for long buckets. However, the implementation is still not synchronized; concurrent put operations with identical hash values can still overwrite each other's entries, leading to data loss.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node
[] tab; Node
p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node
e; K k;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode
)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

In a race where thread A is paused before inserting, thread B may insert first; when A resumes, it overwrites B's entry without any hash check, demonstrating the lack of thread safety.

Summary :

In JDK 1.7, concurrent resize can create circular linked lists, causing infinite loops and possible data loss.

In JDK 1.8, tail‑insertion avoids cycles, but concurrent puts with the same hash still lead to overwriting, so HashMap remains unsafe for multithreaded use.

JavaConcurrencyThread SafetyHashMapJDK8JDK7
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.