Fundamentals 11 min read

Why Does HashMap Become Thread‑Unsafe? Deep Dive into JDK 7 & 8 Resizing Bugs

This article explains why Java's HashMap is not thread‑safe, analyzing the dead‑loop and data‑loss problems caused by resizing in JDK 7 and the overwrite issue in JDK 8, with code examples and step‑by‑step thread interleaving illustrations.

Programmer DD
Programmer DD
Programmer DD
Why Does HashMap Become Thread‑Unsafe? Deep Dive into JDK 7 & 8 Resizing Bugs

1. HashMap in JDK 1.7

We all know that HashMap is not thread‑safe, but the exact reasons are often unclear; this section reveals the root causes in JDK 1.7.

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<Integer, Integer> map = new HashMap<>();
    @Override
    public void run() {
        while (ai.get() < 1000000) {
            map.put(ai.get(), ai.get());
            ai.incrementAndGet();
        }
    }
}

The above code launches several threads that continuously perform put operations on a shared HashMap and AtomicInteger. After several runs, a dead‑loop situation appears, sometimes accompanied by an array‑index‑out‑of‑bounds error.

Stack traces show that the dead loop occurs inside HashMap's resizing function, specifically in the transfer method:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while (null != e) {
            Entry<K,V> 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;
        }
    }
}

During transfer, the original entries are moved to the new table using head‑insertion, which reverses the linked‑list order and can create a circular list when multiple threads resize concurrently.

1.1 How Resizing Causes a Dead Loop

Assume a simple hash function (key mod table size) and an initial table size of 2 with keys 3, 7, 5 all landing in table[1]. After a resize to size 4, the transfer process is illustrated below.

In a single‑threaded scenario the final structure is correct. In a multithreaded scenario, thread A pauses inside the transfer loop at line 11 while thread B completes the resize. When thread A resumes, it writes entries back into the new table using stale local variables, producing a circular chain:

The circular list causes subsequent HashMap traversals to loop forever.

1.2 How Resizing Leads to Data Loss

When thread A and thread B interleave during a resize, thread A may write an entry to the new table that overwrites another entry, causing the original element to disappear and also forming a circular list.

2. HashMap in JDK 1.8

JDK 1.8 optimizes HashMap by appending new nodes to the tail of the bucket list instead of head‑insertion, eliminating the circular‑list problem. However, the structure is still not thread‑safe; concurrent put operations can overwrite each other.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> 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<K,V> 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<K,V>)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;
}

If two threads concurrently insert different keys that hash to the same bucket, both may pass the null‑check at line 6. If thread A pauses before writing, thread B inserts its entry, and when thread A resumes it overwrites thread B's entry, demonstrating a classic race condition.

Summary

HashMap is inherently thread‑unsafe. In JDK 1.7, concurrent resizing can create circular linked lists or lose entries; in JDK 1.8, concurrent put operations can overwrite each other, still leading to incorrect behavior.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaconcurrencythread safetyHashMapJDK8JDK7
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.