Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java ConcurrentHashMap: atomic updates and weakly consistent reads

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

ConcurrentHashMap permits concurrent access to a hash-based map and provides per-key atomic update operations; a group of separate map calls is not automatically one transaction.

Java 8+. The program uses only JDK classes and runs without a framework.

Replace check-then-act with one update

A request counter that calls get, adds one and calls put can lose an update when two workers read the same old value. merge performs the per-key read/remap/write operation as one update, so the program does not coordinate that sequence in application code.

Two workers count accepted requests for one depot. Their task bodies do only map updates, and main waits for both Future results before printing the final count. Waiting matters. A log line emitted immediately after submit can observe a partially completed batch even when every individual update is safe.

Keep remapping functions short and free of unrelated effects. A callback that sends a message or waits for remote I/O mixes the update boundary with work that has different failure rules. Store the new map value inside the update, then perform any separately governed side effect outside it.

A safe map is not a consistent dashboard

ConcurrentHashMap does not accept null keys or null values. A missing get result can therefore represent absence without colliding with a stored null. That convenience does not make a counter across several depot keys a single consistent snapshot.

Iterators are weakly consistent rather than ordinary fail-fast list iterators. A traversal during updates can reflect a mixture of states over time. If a report must reconcile two accounts at exactly one instant, use a larger coordination boundary instead of assuming concurrent map traversal establishes the accounting invariant. Compare locks for multi-field state.

Working program

Java
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class DepotRequestCounts {
    public static void main(String[] args) throws Exception {
        ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
        ExecutorService workers = Executors.newFixedThreadPool(2);
        try {
            Runnable batch = () -> {
                for (int request = 0; request < 400; request++) {
                    counts.merge("DL-01", 1, Integer::sum);
                }
            };
            Future<?> first = workers.submit(batch);
            Future<?> second = workers.submit(batch);
            first.get();
            second.get();
            System.out.println("accepted=" + counts.get("DL-01"));
        } finally {
            workers.shutdown();
        }
    }
}

Output

Output
accepted=800

Cost and failure boundaries

Each batch performs r updates; expected hashed lookup work depends on hash distribution, while contention can add waiting or retry costs. Do not promise wait-free constant latency just because the map is concurrent. The stored membership uses O(k) space for k depot keys, and boxed counter replacements add allocation pressure.

The sample’s counts stay far below int overflow. A long-lived counter needs a range policy, rotation or a larger representation. LongAdder can reduce contention for aggregation but has different snapshot semantics. A bounded worker pool limits simultaneous workers; it does not itself bound every upstream task queue.

Common Mistakes

  • Do not compose get and put and call the resulting increment atomic.
  • Do not perform slow external work inside a remapping callback.
  • Do not infer a multi-key transaction from per-key guarantees.

Apply this contract in Spring

Spring cache keys: separate tenants and test the loader count. These lessons keep framework assembly separate from the Java contract.

java
concurrent-map
Storage details