Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
3.0 KiB
hashmap-vs-concurrenthashmap
Runnable companion for the ankurm.com article HashMap vs ConcurrentHashMap in Java: Internals, Thread Safety and Interview Answers.
Everything the article quotes about JDK internals was read out of the running JDK (reflection) and
cross-checked against the java.util.HashMap and java.util.concurrent.ConcurrentHashMap source in
the JDK's own lib/src.zip. Nothing is quoted from memory.
Versions
| Component | Version | Verified against |
|---|---|---|
| JDK | 25.0.5+8 (Temurin, LTS) | java -version; source from this JDK's lib/src.zip |
| JUnit | 6.1.3 | maven-metadata.xml on Maven Central (latest GA at time of writing) |
| Maven | 3.9.11 | mvn -version |
Quickstart
export JAVA_HOME=/path/to/jdk-25 && export PATH="$JAVA_HOME/bin:$PATH"
cd hashmap-vs-concurrenthashmap
mvn test # 7 tests, deterministic; rewrites output/01-04 and 06-08
./scripts/run-all.sh # also regenerates output/05 (the measured, run-to-run-varying one)
First Maven run must be online. The tests reflect into JDK-internal fields, so surefire is configured
with --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED
(see pom.xml). This is a learning aid; never reflect into JDK internals in production code.
Captured output (output/)
| File | Written by | Deterministic? |
|---|---|---|
01-jdk-constants.txt |
Jdk25ConstantsTest |
yes: TREEIFY_THRESHOLD, MIN_TREEIFY_CAPACITY, MOVED, TREEBIN ... |
02-sizectl-lifecycle.txt |
SizeCtlTest |
yes: sizeCtl / threshold / baseCount through a map's life |
03-treeify-compare.txt |
TreeifyCompareTest |
yes: 12 colliding keys, table length and bin type per insert |
04-forced-lost-update.txt |
ForcedInterleavingTest |
yes: lost update with the bad timing forced by a latch |
05-measured-lost-updates.txt |
LostUpdateDemo (via run-all.sh) |
no: counts differ on every run and machine; no test asserts them |
06-recursive-update.txt |
RecursiveUpdateTest |
yes: computeIfAbsent recursion outcomes |
07-null-handling.txt |
NullHandlingTest |
yes |
08-atomic-updates-exact.txt |
AtomicUpdatesTest |
yes: merge/compute/locked total is exact every run |
Source
| File | Purpose |
|---|---|
Internals.java |
reflective readers: constants, sizeCtl, table, baseCount, threshold, bin class |
CollidingKey.java |
key with a caller-chosen hashCode() to force one bin |
LostUpdateDemo.java |
8 threads x 200,000 increments of one key, 5 rounds, six variants |
RecursiveUpdateDemo.java |
computeIfAbsent re-entering its own map |
The older benchmark and null-handling demos the article also cites live in
java-core-examples/hashmap-concurrenthashmap.
For fail-fast iterators and bucket layout see the sibling collections module.