Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
56 lines
3.0 KiB
Markdown
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.
|