Why HashMap Can Deadlock and Crash Your JVM – Hidden Risks of Concurrent Access

This article explains how using a non‑thread‑safe HashMap in a multithreaded Java program can cause deadlocks, CPU spikes, and memory leaks, analyzes the underlying cause in the put method, and presents safer alternatives such as Collections.synchronizedMap and ConcurrentHashMap.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Why HashMap Can Deadlock and Crash Your JVM – Hidden Risks of Concurrent Access

Introduction

Most Java developers know that HashMap is not thread‑safe, but its concurrency problems go beyond simple data corruption. Under multithreaded access it can cause deadlocks that lead to 100% CPU usage and memory exhaustion.

1. Sample Program

The following program launches 100 threads, each inserting 50,000 entries into a shared HashMap. The expectation is a final size of 5,000, but the program often hangs, spikes CPU to 100%, and consumes excessive memory.

import java.util.HashMap;
import java.util.Map;

public class HashMapManyThread {
    static Map<String, String> map = new HashMap<String, String>(16);

    public static class TestHashMapThread implements Runnable {
        int start = 0;
        public TestHashMapThread(int start) { this.start = start; }
        @Override
        public void run() {
            for (int i = 0; i < 100000; i += 2) {
                System.out.println("--puting----");
                map.put(Integer.toString(i), String.valueOf(Math.random() * 100));
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new TestHashMapThread(i));
        }
        for (int i = 0; i < 100; i++) {
            threads[i].start();
        }
        System.out.println(map.size());
    }
}

2. Cause Analysis

Running the program shows all threads stuck in an infinite loop inside HashMap.put. The relevant source of HashMap.put (line 374) iterates over a bucket’s linked list:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) { // line 374
        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++;
    addEntry(hash, key, value, i);
    return null;
}

During concurrent traversal, two threads can corrupt the linked‑list pointers, creating a cycle (e.g., e1.next points to e2 while e2.next points back to e1). This satisfies the four classic deadlock conditions:

Mutual exclusion – both threads hold exclusive access to nodes.

Hold and wait – each thread holds one node while requesting the other.

No preemption – nodes are released only when the thread finishes.

Circular wait – the two nodes form a circular chain.

3. Deadlock Conditions

The article reviews the four necessary conditions for deadlock and shows how the corrupted linked list in HashMap meets each one.

4. Solutions

4.1 Use Collections.synchronizedMap

Wrap the map with Collections.synchronizedMap to serialize all accesses.

static Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

The wrapper synchronizes on an internal mutex, ensuring only one thread executes put at a time. This is simple but can degrade performance due to blocking.

4.2 Use ConcurrentHashMap

Replace HashMap with ConcurrentHashMap, which employs fine‑grained locking (via ReentrantLock) to achieve thread safety without the severe contention of a fully synchronized map.

static Map<String, String> map = new ConcurrentHashMap<String, String>();

public V put(K key, V value) {
    if (value == null) throw new NullPointerException();
    int hash = hash(key.hashCode());
    return segmentFor(hash).put(key, hash, value, false);
}

V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock(); // acquire segment lock
    try {
        // ... standard insertion logic with rehashing ...
    } finally {
        unlock();
    }
}

Because each segment locks independently, concurrency is much higher while still preventing the deadlock scenario.

5. Conclusion

Using a plain HashMap in a multithreaded environment can cause deadlocks, CPU spikes, and memory leaks. Developers should avoid it for concurrent use and prefer ConcurrentHashMap or a synchronized wrapper, understanding the trade‑offs between simplicity and performance.

Source: https://www.cnblogs.com/wyq178/archive/2004/01/13/8676655.html

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.

deadlockHashMapConcurrentHashMapCollections.synchronizedMap
ITFLY8 Architecture Home
Written by

ITFLY8 Architecture Home

ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.

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.