Understanding Java ConcurrentHashMap: Initialization, Null Handling, Resizing, Data Structure, Operations, and Thread‑Safety
This article explains Java 1.8 ConcurrentHashMap’s default capacity, null‑key/value restrictions, resizing behavior, internal array‑list‑tree structure, node fields, put and get processes, thread‑safety mechanisms, iterator consistency, and differences between JDK 7 and JDK 8 implementations.
1 ConcurrentHashMap default initial capacity
From the static variables in the ConcurrentHashMap class, the default initial capacity is 16.
2 Can ConcurrentHashMap keys or values be null?
No; a null key or value throws a NullPointerException.
3 How many times does ConcurrentHashMap resize?
It doubles the capacity; the transfer method creates a new node array twice the size of the original.
4 What is the data structure of ConcurrentHashMap?
In Java 1.8 it consists of an array, linked lists, and red‑black trees.
5 What does each node contain?
Each node implements Map.Entry<K,V> and stores hash, key, value, and a next reference; the value and next fields are declared volatile for visibility across threads.
6 How does the put operation work?
The overall flow is similar to HashMap and includes the following steps:
If the bucket array is uninitialized, initialize it.
If the target bucket is empty, try to insert the element at the first position.
If a resize is in progress, the thread joins the resize.
If the bucket is non‑empty and not being migrated, lock the bucket (segment lock).
If the bucket stores elements as a linked list, search or insert in the list.
If the bucket stores elements as a red‑black tree, search or insert in the tree.
If the element already exists, return the old value.
If the element does not exist, increment the map size and check whether a resize is needed.
The locks used during insertion include spin lock, CAS, synchronized, and segment lock.
7 Is insertion tail‑or head‑insertion in Java 1.8?
Tail insertion is used, as shown in the put method.
8 When does a linked list convert to a red‑black tree?
When the list length exceeds 8 and the array size exceeds 64. The conversion occurs only if the node array length is at least MIN_TREEIFY_CAPACITY (default 64); otherwise the array is first doubled.
9 How does the get operation work?
Compute the hash value.
Locate the array index using (n‑1) & h.
Perform the appropriate lookup based on the node type at that position.
If the slot is null, return null. If the node matches the key, return its value. If the node’s hash is negative, it indicates a resize or a red‑black tree, which is handled by the find method. Otherwise, traverse the linked list.
10 How is size calculated?
The size calculation is handled in the resize logic and the addCount() method, which is invoked from the put operation.
11 Constructors of ConcurrentHashMap
There are five constructors; the code is shown below:
// No‑arg constructor
public ConcurrentHashMap() { }
// Constructor with initial capacity
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = (initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1);
this.sizeCtl = cap;
}
// Constructor with a map
public ConcurrentHashMap(Map
m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
// Constructor with initial capacity and load factor
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
// Full constructor with concurrency level
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel)
initialCapacity = concurrencyLevel;
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}12 What techniques ensure thread safety?
JDK 1.7 uses Segment + HashEntry; JDK 1.8 replaces Segment with Node+CAS+Synchronized .
13 Does get need locking?
No; get uses unsafe operations to achieve thread safety without locking.
14 Iterator consistency
ConcurrentHashMap provides weakly consistent iterators, while HashMap’s iterators are strongly consistent and throw ConcurrentModificationException on structural changes.
15 Differences between JDK 1.7 and JDK 1.8 implementations
JDK 1.8 reduces lock granularity: JDK 1.7 locks at the Segment level (multiple HashEntry objects), whereas JDK 1.8 locks at the Node level. Data structures differ: JDK 1.7 uses Segment+HashEntry; JDK 1.8 uses array+linked list+red‑black tree+CAS+ synchronized.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.