Files
java-interview-lab/hashmap-vs-concurrenthashmap/README.md
T

56 lines
3.0 KiB
Markdown

# hashmap-vs-concurrenthashmap
Runnable companion for the ankurm.com article
[HashMap vs ConcurrentHashMap in Java: Internals, Thread Safety and Interview Answers](https://ankurm.com/java-hashmap-vs-concurrenthashmap-complete-interview-guide/).
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
```bash
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](https://ankurm.com/git.app/asmhatre/java-core-examples/src/branch/main/hashmap-concurrenthashmap).
For fail-fast iterators and bucket layout see the sibling [`collections`](../collections/) module.