Files
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

54 lines
2.8 KiB
Markdown

# 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
```bash
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](../LICENSE).