Unlocking the Secrets of Java’s ConcurrentHashMap: From JDK 7 to JDK 8
This article provides a comprehensive, step‑by‑step analysis of Java's ConcurrentHashMap, covering its evolution from JDK 7’s segment‑lock design to JDK 8’s bucket‑level CAS and synchronized approach, complete with macro diagrams, microscopic source‑code walkthroughs, hash calculations, resizing mechanisms, and practical usage tips.
01 ConcurrentHashMap Overview
ConcurrentHashMap is the de‑facto standard container for high‑concurrency scenarios in Java. It replaces the non‑thread‑safe HashMap and the heavyweight HashTable, offering fine‑grained locking and lock‑free reads.
02 Macro View: Evolution Diagram
HashMap – non‑thread‑safe, uses an array of Entry objects with singly linked lists. In JDK 8 it adds a red‑black tree when a bucket’s list length reaches 8 and the table size is at least 64.
HashTable – early thread‑safe implementation that locks the entire table, leading to poor scalability.
ConcurrentHashMap – provides high‑performance thread safety. JDK 7 uses segmented locks ( Segment array) while JDK 8 removes segments and adopts a bucket‑level design with Node + linked list/red‑black tree + CAS + synchronized.
03 Microscopic View: Source Code Dissection
JDK 7 Implementation
final Segment<K,V>[] segments; // segment array
static final class Segment<K,V> extends ReentrantLock {
transient volatile HashEntry<K,V>[] table; // per‑segment hash table
// ...
}
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value; // volatile for visibility
volatile HashEntry<K,V> next; // volatile pointer
// ...
}The default concurrency level is 16, creating 16 segments. Each segment’s capacity is calculated as initialCapacity / concurrencyLevel, rounded up to the nearest power of two.
int segmentCapacity = initialCapacity / concurrencyLevel;
segmentCapacity = roundUpToPowerOf2(segmentCapacity);
threshold = (int)(segmentCapacity * loadFactor); // default load factor 0.75When the first entry is inserted, size reaches threshold and triggers a segment‑level resize.
JDK 8 Implementation
static final class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val; // volatile for visibility
volatile Node<K,V> next; // volatile pointer
// ...
}
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root; // red‑black tree root
volatile TreeNode<K,V> first; // linked‑list head for iteration
volatile Thread waiter; // for read‑write lock control
// ...
}Hash calculation is simplified to:
static final int spread(int h) {
return (h ^ (h >>> 16)) & 0x7fffffff;
}When a bucket’s list length reaches 8 and the table size is less than 64, the list is transformed into a red‑black tree; otherwise the table is resized.
04 Core Method Analysis – put()
JDK 8 put()
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
// ... core insertion logic ...
}The method first checks for nulls, computes the hash, then attempts a lock‑free CAS insertion. If the bucket is empty, it inserts directly; otherwise it synchronizes on the bucket head, handles collisions (list or tree), and may trigger resizing.
05 Resizing Mechanisms Comparison
JDK 7 – each Segment resizes independently when its element count exceeds capacity × loadFactor. The new array is twice the old size, and entries are rehashed within that segment.
void rehash() {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY) return;
HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity << 1);
threshold = (int)(newTable.length * loadFactor);
// migrate entries
table = newTable;
}JDK 8 – the whole table resizes together. Multiple threads can cooperate in the transfer; each thread locks only the bucket it is moving. A ForwardingNode marks migrated buckets, allowing reads to continue on the old table while writes assist the transfer.
06 Hash Calculation Deep Dive
JDK 7 hash() performs several bit‑shifts and XORs to disperse bits, but does not use the final “xor‑shift” perturbation found in HashMap.
private int hash(Object k) {
int h = k.hashCode();
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}JDK 8 spread() uses a single xor‑shift and masks the sign bit, making it faster and more suitable for CAS‑based bucket selection.
07 Usage Recommendations
For simple read‑only or low‑contention scenarios, HashMap is sufficient.
In high‑concurrency environments, prefer ConcurrentHashMap but still synchronize compound actions (e.g., check‑then‑act).
Iterators are weakly consistent; they reflect some, but not necessarily all, modifications.
Bulk operations like putAll are not atomic.
08 Summary
ConcurrentHashMap combines an array, linked list/red‑black tree, CAS, and fine‑grained synchronization to achieve near‑optimal performance in multithreaded Java applications. Understanding its macro evolution, microscopic implementation, hash computation, and resizing strategies enables developers to write more efficient and correct concurrent code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer Null's Self-Cultivation
Focused on backend development technologies and sharing knowledge from programming project experiences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
