Detailed Explanation of Java ConcurrentHashMap Source Code: Constructors, put, get, resize, and Concurrency Mechanisms
This article provides an in‑depth analysis of the Java ConcurrentHashMap implementation, covering its constructor logic, hash‑table sizing, the core put and get operations, element counting with addCount, multi‑threaded resizing via transfer, treeification of bins, removal, computeIfAbsent, and the design choices that forbid null keys and values to simplify concurrency handling.
The article introduces the source code of ConcurrentHashMap , a thread‑safe hash table whose design aims to minimize update contention while keeping space usage comparable to HashMap . It explains that Java 8 and later versions rely on CAS, synchronized , spin‑retry, and volatile to guarantee safety.
Constructor
The constructor validates parameters, adjusts the initial capacity to the concurrency level, computes the table size using tableSizeFor , and stores the result in sizeCtl . The helper method tableSizeFor rounds the size up to the nearest power of two using bit‑wise operations.
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;
}
private static final int tableSizeFor(int c) {
int n = -1 >>> Integer.numberOfLeadingZeros(c - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}put Method
The put operation delegates to putVal , which performs null checks, computes a spread hash, and attempts a lock‑free insertion using CAS. If the bucket is empty, the new node is installed directly; otherwise the method synchronizes on the first node, walks the chain or tree, and inserts or updates the value while maintaining the bin count for possible treeification.
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
for (Node
[] tab = table;;) {
Node
f; int n, i, fh; K fk; V fv;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<>(hash, key, value))) break;
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else { /* lock, chain or tree handling */ }
}
addCount(1L, binCount);
return null;
}Element Counting – addCount
The addCount method updates the map size using a fast baseCount CAS; on contention it falls back to an array of CounterCell objects, similar to LongAdder , to reduce synchronization overhead. When the total count exceeds the resize threshold, it triggers a resize.
Resizing – transfer
Resizing is coordinated by multiple threads. A global transferIndex and a per‑thread stride determine the range of buckets each thread processes. The method creates a new table of double size, installs a ForwardingNode in each moved bucket, and moves entries either as linked lists or red‑black trees, using bit‑wise calculations to decide low‑ or high‑position placement.
private final void transfer(Node
[] tab, Node
[] nextTab) {
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
if (nextTab == null) {
Node
[] nt = (Node
[])new Node
[n << 1];
nextTab = nt;
nextTable = nt;
transferIndex = n;
}
/* each thread repeatedly claims a range [bound, i] and moves entries */
}Treeification – treeifyBin
When a bin’s chain exceeds TREEIFY_THRESHOLD (8) and the table is at least 64 slots, the chain is converted into a red‑black tree to improve lookup performance. If the table is smaller, the method first expands the table.
get and remove
The get method computes the hash, locates the bucket, and either returns the value directly, traverses a list, or delegates to the node’s find method for trees or forwarding nodes. The remove method locks the bucket, unlinks the target node, and calls addCount(-1L, -1) to adjust the size.
computeIfAbsent
This method demonstrates the use of a ReservationNode as a placeholder while the mapping function computes the value. After the value is obtained, the placeholder is replaced with a regular node, and the bin may be treeified if it becomes large.
Why null keys/values are disallowed
Unlike HashMap , ConcurrentHashMap forbids null keys and values to simplify concurrent algorithms; a null bucket unambiguously means “empty”, and get can safely return null to indicate absence without extra sentinel handling.
Code organization
The class groups static constants and helper methods first, then fields, followed by public API methods, and finally internal utilities such as resizing, tree structures, iterators, and bulk operations, providing a clear layout for maintainability.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
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.