Fundamentals 15 min read

Is Java's ConcurrentHashMap Strongly Consistent? Unveiling the Truth

This article explains the difference between strong and weak consistency, explores Java's memory model, visibility and ordering issues, and shows how Java 8 redesigns ConcurrentHashMap with CAS and Unsafe to achieve strong consistency for put and get operations.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Is Java's ConcurrentHashMap Strongly Consistent? Unveiling the Truth

Hello everyone, I am Sanyou.

Last week I saw a lecturer on GeekTime discuss whether ConcurrentHashMap (CHM) provides strong or weak consistency, and a widely circulated explanation was presented.

Before answering, we need to clarify two questions:

What are strong and weak consistency?

If get is not locked and cannot instantly see put 's data, does locking guarantee immediate visibility, or are there other ways?

Strong vs. Weak Consistency

Strong Consistency

Consistency refers to data consistency across multiple replicas and can be strong or weak.

Strong consistency (also called atomic or linearizable consistency) must satisfy two requirements:

Every read must immediately see the most recent write of a data item.

All processes see operations in the same order as the global clock.

In simple terms, if thread A modifies data first, all subsequent operations by thread B should immediately (or real‑time ) observe A's modification.

Weak Consistency

Weak consistency means that after an update, an immediate read may miss the new data or see only part of it; if after some time all threads can see the data, it is called eventual consistency, a special case of weak consistency.

To achieve strong consistency in Java we must understand two concepts: visibility and ordering.

Root Causes of Consistency: Visibility and Ordering

Visibility

First, we need to understand Java's memory model.

The JVM memory model consists of thread‑local areas (program counter, virtual machine stack, native method stack) and shared areas (heap and method area). The heap is where consistency problems arise.

CPU fetches data from registers; if not present, it loads from memory into a three‑level cache (cache line ~64 B). This speeds up execution but can cause stale data because a thread may keep a value in its registers or cache without flushing it to main memory.

Example code:

<code>// Thread 1
int i = 0;
i = 10;

// Thread 2
j = i;</code>

After thread 1 writes i = 10 , the value may stay in the CPU cache and not be flushed to memory. When thread 2 reads, it may still see 0 , demonstrating a visibility‑induced inconsistency.

To solve this, the CPU must write shared variables back to memory immediately and invalidate other CPUs' cache lines (e.g., via the MESI protocol).

Ordering

Instruction reordering can also cause inconsistency.

<code>int x = 1;   //①
boolean flag = true; //②
int y = x + 1; //③</code>

The compiler may reorder these statements, e.g., executing ①③②, because keeping x in a register speeds up y 's computation. This can lead to unexpected results.

<code>public class Reordering {
    private static boolean flag;
    private static int num;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (!flag) { Thread.yield(); }
            System.out.println(num);
        }, "t1");
        t1.start();
        num = 5; //①
        flag = true; //②
    }
}</code>

If the writes are reordered, the printed value may be 0 instead of 5 . Adding memory barriers (locks) prevents such reordering, establishing a happens‑before relationship.

How to guarantee visibility and ordering?

Using volatile , synchronized , or explicit locks establishes happens‑before semantics, ensuring that writes become visible to other threads in order.

Now, regarding CHM:

In Java 7 and earlier, put is locked while get is lock‑free, leading to weak consistency for get . The array slot is assigned a new Node without volatile semantics, so other threads may not see the update immediately.

<code>V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock();
    ...
    tab[index] = new HashEntry<>(...);
    unlock();
}</code>
<code>V get(Object key, int hash) {
    if (count != 0) {
        Node e = getFirst(hash);
        while (e != null) {
            if (e.hash == hash && key.equals(e.key)) {
                V v = e.value;
                if (v != null) return v;
                return readValueUnderLock(e);
            }
            e = e.next;
        }
    }
    return null;
}</code>

Thus, before Java 8, CHM's get / put exhibited weak consistency.

Java 8 redesigns CHM using sun.misc.Unsafe :

Key points:

Writes use compareAndSwapObject (CAS) to update the array element in memory.

Reads use getObjectVolatile to fetch the element with volatile semantics.

Because both read and write operate directly on memory, put and get become strongly consistent in Java 8 and later.

Note that not all CHM operations are strongly consistent; for example, size() remains weakly consistent.

Conclusion

Java 8 dramatically refactors CHM, removing the segment design, adding per‑bucket locks, using red‑black trees, and allowing concurrent resizing. It combines CAS, synchronized , generics, and clever algorithms to achieve high performance while providing strong consistency for core put / get operations. Understanding these mechanisms helps you verify claims in source code and avoid common misconceptions.

JavaConcurrencyConcurrentHashMapMemory Modelconsistency
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login 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.