Deep Dive into Java Concurrency: Analyzing the ConcurrentHashMap Source (Part 7)

This article thoroughly examines Java's ConcurrentHashMap by tracing its evolution from JDK 1.5's segment‑lock design to JDK 8's CAS‑plus‑synchronized implementation, detailing internal structures, put/get algorithms, resizing mechanics, performance trade‑offs, and common interview questions.

Coder Trainee
Coder Trainee
Coder Trainee
Deep Dive into Java Concurrency: Analyzing the ConcurrentHashMap Source (Part 7)

Evolution of ConcurrentHashMap

JDK 1.5‑1.6 used a segment‑lock design: multiple Segment objects each held an independent lock, which caused high memory consumption and fixed concurrency. JDK 1.7 refined the segment lock, allowing a segment to resize without locking the whole table, but the number of segments remained a scalability limit. JDK 1.8 replaced segment locks with a combination of CAS operations and synchronized blocks, achieving finer‑grained locking, lower memory overhead, and higher concurrency.

JDK 1.7 implementation – segment lock

Core design

┌─────────────────────────────────────────────────────────────────┐
│               JDK 1.7 ConcurrentHashMap                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ConcurrentHashMap                                            │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Segment[] segments (default 16)                  │   │
│   │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
│   │  │Segment 0│ │Segment 1│ │Segment 2│ │Segment 3│   │
│   │  │ (lock) │ │ (lock) │ │ (lock) │ │ (lock) │   │
│   │  │HashEntry│ │HashEntry│ │HashEntry│ │HashEntry│   │
│   │  │ array   │ │ array   │ │ array   │ │ array   │   │
│   │  └─────────┘ └─────────┘ └─────────┘ └─────────┘   │
│   └─────────────────────────────────────────────────────┘   │
│                                                                 │
│  put operation: locate Segment → lock Segment → modify HashEntry array │
│  Different Segments can operate concurrently                │
└─────────────────────────────────────────────────────────────────┘

Code structure

public class ConcurrentHashMap<K,V> {
    // Segment array, default 16
    final Segment<K,V>[] segments;
    static final class Segment<K,V> extends ReentrantLock {
        transient volatile HashEntry<K,V>[] table;
        transient int count;
        transient int modCount;
        transient int threshold;
        final float loadFactor;
    }
    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
    }
}

put operation flow

public V put(K key, V value) {
    int hash = hash(key);
    int index = (hash >>> segmentShift) & segmentMask;
    Segment<K,V> s = segments[index]; // locate Segment
    return s.put(key, hash, value, false); // lock Segment internally
}

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock(); // ReentrantLock
    try {
        // manipulate HashEntry array, handle resize
    } finally {
        unlock();
    }
}

JDK 1.8 implementation – CAS + synchronized

Core changes

Lock mechanism : ReentrantLock (Segment) → CAS + synchronized

Data structure : Segment + HashEntry → Node array + Red‑Black tree

Lock granularity : Segment level (fixed 16) → Node level (per bucket)

Resize : Each Segment resizes independently → Whole table resizes with multi‑thread assistance

Core data structures

public class ConcurrentHashMap<K,V> {
    // Node array – core storage
    transient volatile Node<K,V>[] table;
    // Used during resize
    private transient volatile Node<K,V>[] nextTable;
    // Size control: negative values indicate resizing, -1 means uninitialized
    private transient volatile int sizeCtl;
    // Marker used during resize
    private transient volatile int transferIndex;
    // Base count for size() calculation
    private transient volatile long baseCount;
    // Counter cells for concurrent counting
    private transient volatile CounterCell[] counterCells;
    static final class Node<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    }
    static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;
        boolean red;
    }
    static final class TreeBin<K,V> extends Node<K,V> {
        TreeNode<K,V> root;
        volatile TreeNode<K,V> first;
        volatile Thread waiter;
    }
}

put operation source

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;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 1. Initialise table if null
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 2. CAS insert if bucket empty
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        // 3. Help with resize if MOVED flag seen
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // 4. Lock bucket and insert
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // linked list
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent) e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value, null);
                                break;
                            }
                        }
                    } else if (f instanceof TreeBin) { // red‑black tree
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent) p.val = value;
                        }
                    }
                }
            }
            // 5. Treeify if binCount exceeds threshold
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null) return oldVal;
                break;
            }
        }
        addCount(1L, binCount);
        return null;
    }
}

get operation (lock‑free)

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        // 1. Check first node
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // 2. If red‑black tree or resize marker
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 3. Traverse linked list
        while ((e = e.next) != null) {
            if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

Resizing mechanism (core difficulty)

When resize is triggered

Current table length is less than the maximum capacity.

Number of elements exceeds sizeCtl (load factor × table length).

Multi‑thread assisted resize

┌─────────────────────────────────────────────────────────────────┐
│               Multi‑Thread Assisted Resize                     │
├─────────────────────────────────────────────────────────────────┤
│ Thread A detects need to resize → creates nextTable (double size) │
│ → migrates each bucket to new table → marks migrated buckets with │
│   ForwardingNode (hash = MOVED)                                 │
│ Other threads encountering MOVED help finish the transfer       │
└─────────────────────────────────────────────────────────────────┘

ForwardingNode marker

static final class ForwardingNode<K,V> extends Node<K,V> {
    final Node<K,V>[] nextTable;
    ForwardingNode(Node<K,V>[] tab) {
        super(MOVED, null, null, null);
        this.nextTable = tab;
    }
}

JDK 7 vs JDK 8 comparison (old‑school vs modern)

Lock granularity : Segment level (fixed 16) vs Node level (per bucket).

Lock implementation : ReentrantLock vs synchronized + CAS.

Maximum concurrency : Fixed by segment count vs equal to table length.

Data structure : Linked list vs linked list + Red‑Black tree.

Resize : Independent per segment vs whole‑table multi‑thread assisted.

Performance : Medium vs High.

Memory usage : High (segment array) vs Low.

Common interview questions

Why did JDK 8 drop segment locks?

Memory consumption : Segment array and a ReentrantLock per segment increase memory usage. Fixed concurrency : The number of segments is static, limiting scalability. Lock contention : Operations within the same segment serialize. Optimization space : CAS + synchronized can shrink lock granularity to a single node, raising concurrency.

How is size() implemented?

In JDK 8, size() aggregates baseCount and the values in the CounterCell array. CounterCell distributes counting across threads to reduce contention.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaConcurrencyMultithreadingConcurrentHashMapJDK8JDK7DataStructures
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.