Files
java-core-examples/concurrency-interview/README.md
T
asmhatreandClaude Sonnet 5 202f2f18a5 concurrency-interview: companion code for the Top 40 interview post
Adds the concurrency-interview module: a real race condition with its
actual lost-update count, why volatile alone doesn't fix it, a real
JVM-detected deadlock (ThreadMXBean.findDeadlockedThreads()) with the
fix, and ScopedValue's exact child-thread inheritance rules (finalized
in JDK 25 via JEP 506, demonstrated against StructuredTaskScope, still
preview per JEP 505/525). 4 runnable demos, 1 JUnit test class (9
tests), 5 captured output transcripts.

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

79 lines
4.7 KiB
Markdown

# concurrency-interview
Companion code for the ankurm.com post *"Top 40 Java Concurrency Interview Questions and Answers
(2026)."* Seventh module in `java-core-examples`, the Java-core / concurrency series - this one
backs a hub-style Q&A post rather than a single deep dive.
Most of the 40 questions in that post link out to the deep, already-verified demos in this repo's
other modules (locks, atomics, jmm, synchronizers, executors, vt-pinning) and to a few of this
site's existing standalone posts. The four demos here exist because their questions weren't
already proven anywhere else in the series: a real race condition and its actual lost-update
count, why `volatile` alone doesn't fix it, a real JVM-detected deadlock (and the fix), and exactly
which threads a `ScopedValue` binding does and doesn't reach.
## Versions this was built and tested against
| Component | Version | Notes |
|---|---|---|
| JDK | 25.0.4.1+1 (Temurin, LTS) | Every demo needs `--enable-preview` - see below. |
| JUnit Jupiter | 5.11.0 | Correctness tests, 5 repeats for the one timing-sensitive case. |
| Maven | 3.9.11 | |
| Hardware | 2 vCPU x86-64 VM | Same sandbox as the rest of this series. |
**Why `--enable-preview` for everything, even the demos that don't touch a preview API:** this
module is compiled as one unit, and `ScopedValueDemo` needs `StructuredTaskScope` (still preview
in JDK 25/26, per JEP 505/525) to demonstrate scoped-value inheritance into a child thread.
`ScopedValue` itself was finalized in JDK 25 via JEP 506 and needs no flag on its own.
## Quickstart
```bash
export JAVA_HOME=/path/to/jdk-25
mvn package
java --enable-preview -cp target/classes com.ankurm.concurrencyinterview.RaceConditionDemo
java --enable-preview -cp target/classes com.ankurm.concurrencyinterview.DeadlockDemo
```
`scripts/run-all.sh` regenerates every file in `output/` (needs `JDK25_HOME`).
## What's in here
| File | What it shows |
|---|---|
| `.../RaceConditionDemo.java` | A plain `int` incremented by 8 threads, 200,000 times each, next to an `AtomicInteger` doing identical work - the real lost-update count, not an assertion that one exists. |
| `.../VolatileNotEnoughDemo.java` | The same workload against a `volatile int` - visibility is guaranteed, atomicity is not, and the loss is just as real. |
| `.../DeadlockDemo.java` | Two threads, two locks, acquired in opposite order - a genuine deadlock, detected with `ThreadMXBean.findDeadlockedThreads()`, then a second run with consistent lock ordering that completes normally. |
| `.../ScopedValueDemo.java` | `ScopedValue` visible to same-thread direct and indirect callees; NOT inherited by a plain `new Thread()`; inherited by a thread forked from `StructuredTaskScope`. |
| `src/test/.../InterviewClaimsTest.java` | Asserts what's actually guaranteed: `AtomicInteger` never loses an update (5 repeats), and `ScopedValue`'s exact inheritance rule - deliberately does NOT assert on the raw-int or volatile-int race outcomes, since those are genuinely non-deterministic. |
| `output/01`-`04` | Each demo's real run. |
| `output/05` | JUnit correctness run. |
## Reading the results honestly (2-vCPU sandbox)
**The race condition lost 351,504 of 1,600,000 increments this run** (`output/01`) - not "some"
or "a few," an exact, real number from an actual run, which is the whole point of not just
asserting that `count++` is unsafe. Rerun it and the number will be different; it won't be zero on
this hardware with this workload.
**`volatile` lost 744,982 updates under the identical workload** (`output/02`). `volatile`
guarantees every thread observes the latest write and prevents instruction reordering around it -
it says nothing about two threads reading the same value before either writes back, which is
exactly what `count++` (read, add, write) allows. The fix for a shared counter is `AtomicInteger`
or a lock, not the `volatile` keyword.
**The deadlock in `output/03` is real, not simulated** - `ThreadMXBean.findDeadlockedThreads()` is
the same API a monitoring agent or `jstack` uses, and it reports both threads, each blocked on the
lock the other holds. The fix demonstrated right after it isn't "add more locking" - it's ensuring
every thread that needs both locks acquires them in the same order, which makes the circular-wait
condition structurally impossible.
**`ScopedValue` inheritance in `output/04` is exactly as narrow as the JDK docs say, confirmed
rather than assumed:** bound on the main thread, visible to nested method calls on that same
thread; a plain `new Thread()` reports `isBound() = false`; only the `StructuredTaskScope.fork()`'d
thread sees the binding. Reaching for `ScopedValue` and expecting it to "just work" across a
hand-rolled `new Thread()` is a real interview trap, not a hypothetical one.
## License
MIT - see the [repo-wide LICENSE](../LICENSE).