Question 22 · Section 10

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.

Language versions: English Russian Ukrainian

🟢 Junior Level

HashMap is not thread-safe. If multiple threads access HashMap simultaneously, problems can arise:

  1. Data loss — one thread may overwrite another’s result
  2. Unpredictable behaviorget() may return the wrong value
  3. 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

  1. Thinking fail-fast = safety — this is only best-effort detection
  2. Using HashMap in Spring Singleton — one instance for all requests
  3. 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:

  1. Both see tab[i] == null
  2. Both create newNode
  3. 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)
  • synchronized on 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]]