How HashMap Works in a Multi-threaded Environment?
In Java 7, on parallel resize, list elements were reversed (head insertion). Two threads could create a cyclic reference → infinite loop on get() → 100% CPU.
🟢 Junior Level
HashMap is not thread-safe. If multiple threads access HashMap simultaneously, problems can arise:
- Data loss — one thread may overwrite another’s result
- Unpredictable behavior —
get()may return the wrong value - ConcurrentModificationException — on iteration with simultaneous modification
Problem example:
Map<String, Integer> map = new HashMap<>();
// Main risk — corruption on resize, not on normal insertion
// If keys are different and buckets are different — loss is unlikely
// But on concurrent resize, element loss is possible!
// Worse: if keys are the SAME:
// Thread 1: put("key", 100)
// Thread 2: put("key", 200) → overwrites!
Solution: Use ConcurrentHashMap for multi-threaded access.
🟡 Middle Level
Main Risks
| Risk | Description | Consequences |
|---|---|---|
| Race Condition | Two threads write to the same bucket | Data loss |
| Visibility | Changes not visible to other threads | Stale data |
| Resize | Two threads expand simultaneously | Data loss, corruption |
| Fail-Fast Iterator | Modification during iteration | ConcurrentModificationException |
The Java 7 Problem: Infinite Loop
In Java 7, on parallel resize, list elements were reversed (head insertion). Two threads could create a cyclic reference → infinite loop on get() → 100% CPU.
In Java 8+, this is fixed via tail insertion, but data loss on resize is still possible.
How to Work Safely?
| Method | Mechanism | Performance |
|---|---|---|
ConcurrentHashMap |
CAS + bucket-level locking | High |
Collections.synchronizedMap() |
Single mutex for everything | Low |
Hashtable |
Synchronized methods | Low (deprecated) |
When Can HashMap Be Used in Multi-threading?
Only if it’s populated before threads start and used read-only. Important: safe publication is needed — without volatile/final, another thread may not see the written data.
Map<String, Integer> map = new HashMap<>();
map.put("config", 42);
// Start threads — read only!
Common Mistakes
- Thinking fail-fast = safety — this is only best-effort detection
- Using HashMap in Spring Singleton — one instance for all requests
- Check-then-act without atomicity — TOCTOU race condition
🔴 Senior Level
Internal Race Conditions
// putVal — simplified code:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
Two threads simultaneously:
- Both see
tab[i] == null - Both create
newNode - Both write to
tab[i]— one loses its element
Without synchronization, this is a data race under the Java Memory Model.
Visibility Problem
HashMap fields are not volatile. Even if thread A wrote an element, thread B may not see it in its CPU cache (L1/L2). A memory barrier is needed.
Resize Race Condition
// Two threads simultaneously call resize():
// 1. Both create newTab
// 2. Both copy elements
// 3. Both write `table = newTab`
// Result: one array loses elements
Fail-Fast Mechanism
transient int modCount; // Increments on every modification
// In the iterator:
int expectedModCount = modCount;
public V next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
This is not a synchronization mechanism, but only a debugging aid.
Safe Publication Pattern
HashMap is safe for reading after safe publication:
// Via final field:
private final Map<K, V> map = createAndFillMap();
// Via volatile:
private volatile Map<K, V> map;
// Via AtomicReference:
private final AtomicReference<Map<K, V>> mapRef;
ConcurrentHashMap: Internal Architecture
Java 7: Segment[] (16 segments, each with its own lock)
Java 8+: Node-level locking (synchronized on bucket head) + CAS
In Java 8+:
- CAS for empty bucket (no locking)
synchronizedon bucket head for collisions- Visibility is ensured via Unsafe.compareAndSwap (CAS operations) and memory barriers, not via volatile Node field declarations.
Production Experience
In Spring applications, HashMap as a @Service field — a common bug:
@Service
public class CacheService {
private Map<String, Data> cache = new HashMap<>(); // NOT SAFE!
}
// Multiple HTTP requests = multiple threads
Solution: ConcurrentHashMap or Collections.synchronizedMap().
🎯 Interview Cheat Sheet
Must know:
- HashMap is NOT thread-safe: data race, data loss, corruption on resize
- Safe publication: a Map filled before thread start needs volatile/final for visibility
- Fail-fast iterator (modCount) — a debugging aid, NOT a synchronization mechanism
- Java 7: infinite loop on parallel resize (head insertion); Java 8+: fixed (tail insertion)
- Three safe options: ConcurrentHashMap (CAS + bucket lock), synchronizedMap, Hashtable
- ConcurrentModificationException doesn’t guarantee detection of all races
Common follow-up questions:
- Can HashMap be read from multiple threads? — yes, if safe publication (final/volatile) and nobody writes
- Why is Spring @Service with HashMap a bug? — one instance for all HTTP requests (many threads)
- What is TOCTOU race? — Time-Of-Check-Time-Of-Use: check and action are not atomic
- Visibility without volatile? — another thread may not see written data (CPU cache)
Red flags (DO NOT say):
- “Fail-fast = thread safety” — no, only detection
- “HashMap is safe if keys are different” — no, corruption on resize is possible
- “synchronizedMap = fast solution” — no, single mutex for everything, low performance
Related topics:
- [[21. When Can Time Complexity Become O(n)]]
- [[23. What is ConcurrentHashMap and How Does It Differ from HashMap]]
- [[19. What Happens During Rehashing]]