Files
java-core-examples/hashmap-concurrenthashmap
asmhatreandClaude Sonnet 5 4a74153d87 hashmap-concurrenthashmap: companion code for the HashMap vs ConcurrentHashMap interview guide
Adds JMH put/get throughput benchmarks (synchronizedMap vs ConcurrentHashMap,
swept at 1/2/4/8 threads), a computeIfAbsent recursion trap demo (CHM tolerates
recursion into a different key but throws IllegalStateException on the same
key; plain HashMap throws ConcurrentModificationException even single-threaded),
and a null-handling demo (CHM rejects null keys/values on put() and null keys
on get() too). 7/7 JUnit tests pass; all output/ transcripts captured from
real runs.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FhzLY5p6okFva3qsnsRyvM
2026-09-30 09:29:06 +00:00
..

hashmap-concurrenthashmap

Companion code for the ankurm.com post "Java HashMap vs ConcurrentHashMap: Complete Interview Guide." Ninth module in java-core-examples.

Versions this was built and tested against

Component Version Notes
JDK 25.0.4.1+1 (Temurin, LTS)
JMH 1.37 Throughput mode, 3x1s warmup, 5x1s measurement, 1 fork.
JUnit Jupiter 5.11.0
Maven 3.9.11
Hardware 2 vCPU x86-64 VM Same sandbox as the rest of this series - see the benchmark's own javadoc for what that does and doesn't tell you at higher thread counts.

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn package
java -cp target/classes com.ankurm.hashmapconcurrenthashmap.ComputeIfAbsentRecursionDemo
java -cp target/classes com.ankurm.hashmapconcurrenthashmap.NullHandlingDemo
java -jar target/benchmarks.jar PutGetBenchmark -t 4

scripts/run-all.sh regenerates every file in output/ (needs JDK25_HOME).

What's in here

File What it shows
.../ComputeIfAbsentRecursionDemo.java Three real outcomes of recursing into the same map from inside computeIfAbsent: ConcurrentHashMap tolerates it for a different key, HashMap throws ConcurrentModificationException for a different key even single-threaded, and ConcurrentHashMap throws IllegalStateException: Recursive update for the same key.
.../NullHandlingDemo.java HashMap allows one null key and any number of null values; ConcurrentHashMap rejects null keys and values on put() - and rejects a null key on get() too.
.../PutGetBenchmark.java JMH: an 80% get / 20% put workload against Collections.synchronizedMap(new HashMap<>()) vs ConcurrentHashMap, swept across 1/2/4/8 threads.
src/test/.../MapClaimsTest.java Asserts every claim above.
output/01-02 Each trap demo's real run.
output/03-06 The benchmark sweep, one file per thread count.
output/07 JUnit correctness run.

Reading the results honestly (2-vCPU sandbox)

ConcurrentHashMap beat the synchronized wrapper even at 1 thread (output/03) - every call into synchronizedMap pays for entering and exiting a monitor whether or not another thread is waiting, while ConcurrentHashMap's reads (80% of this workload) are lock-free from the start. The gap widens sharply from 2 threads on (output/04-06): the synchronized wrapper's single monitor means every thread, including readers, queues behind whichever thread currently holds it, while ConcurrentHashMap only locks the bucket a write touches and never locks reads at all. On this 2-vCPU sandbox, throughput for both flattens once thread count exceeds the core count - that's the hardware's ceiling, not evidence either map "stops scaling" at some inherent limit.

License

MIT - see the repo-wide LICENSE.