Rung 5 — Concurrency Playground 🚨 HARD GATE¶
Target month: Shipped by end of M8 = end of February 2027 (started in M7) Calendar deadline: February 28, 2027 — NO SLIPPAGE Phase alignment: Phase 05 — Concurrency & Multithreading Signal level: Senior-signal → this is the first rung where an study partner at a serious product company would say “this candidate is a peer, not a junior”
🚨 This is one of the two hard gates in the ladder. If this rung is not shipped by end of M8, the entire plan is at structural risk. Rung 6 (JVM performance) depends on the concurrency work here as its test target. Rung 8 (ML capstone) depends on you being able to write concurrent Java under load. Slipping this rung by even 4 weeks cascades into losing Rung 6 or Rung 8 outright. See
09_hard_gates.mdfor the full argument.
What It Is¶
A single public repository containing two production-quality, benchmarked concurrency artifacts in Java 21:
Artifact A — A Rate Limiter Library¶
A small library that exports three rate-limiting algorithms, all thread-safe, all with the same interface:
Token bucket (with configurable capacity and refill rate)
Sliding window log (precise, memory-heavier)
Fixed window counter (approximate, memory-cheap)
Each algorithm implements a RateLimiter interface with tryAcquire(int permits) and acquire(int permits) methods. The library ships with:
Correctness tests under contention (100+ threads hammering it, exact expected pass/fail counts asserted)
A JMH benchmark suite comparing all three algorithms across three access patterns (steady, bursty, cold-start)
A README section titled “When to use which” based on your own benchmark data
Artifact B — The Virtual Threads Benchmark¶
A benchmarking harness that answers a specific, publishable question: “For an HTTP-fanout workload, at what concurrency does the virtual-thread executor beat a bounded platform-thread pool, and by how much?”
Concretely:
A local mock HTTP server that responds with 50-500 ms of simulated latency
A client that fires N concurrent requests, N ∈ {10, 100, 1K, 10K, 100K}
Two execution modes:
Executors.newVirtualThreadPerTaskExecutor()vsExecutors.newFixedThreadPool(200)Metrics: p50/p95/p99 latency, throughput, peak memory (RSS), thread count
Output: a CSV + a rendered chart (matplotlib or gnuplot, checked into the repo as PNG)
The blog post publishes the chart with commentary. The chart is the artifact.
Where To Publish¶
Repo:
github.com/RaghulR2003/java-concurrency-playground— public, pinned, this replaces one of your earlier pins if you’re out of slotsBlog: Hashnode primary, dev.to cross-post. Title: “Virtual threads vs platform threads on an HTTP-fanout workload — where the crossover lives.” The chart is the hero image.
LinkedIn: Long-form post with the chart as the image. This is your first post that will get shared by Java-heavy accounts. Write it deliberately.
Java community channels: Post to
r/java, the Foojay mailing list, and Inside Java newsletter suggestion inbox. Virtual threads content is high-demand.
Acceptance Criteria¶
For Artifact A — Rate Limiter¶
Three algorithms implemented, all implementing the same
RateLimiterinterfaceEach algorithm has a correctness test that runs 128 threads for 10 seconds and asserts the aggregate rate is within 1% of the configured limit
Each algorithm has a race-condition test using Lincheck or a hand-rolled interleaving harness
Zero use of
synchronizedblocks where anAtomicLong/LongAdder/StampedLockwould be more appropriate — and the README explains each choiceJMH benchmarks published for all three, results as a table in the README
For Artifact B — Virtual Threads Benchmark¶
Mock HTTP server implemented (embedded Jetty or
com.sun.net.httpserver.HttpServer)Benchmark runs N ∈ {10, 100, 1K, 10K, 100K} for both executor types
Results stored as CSV, checked into
results/folderChart rendered and checked in as
results/latency-vs-concurrency.pngrun.shreproduces the entire benchmark in one command (with documented JDK version, heap settings, hardware caveat)Benchmark handles the pinning problem: results explicitly note whether virtual threads pinned to carriers and how you detected it
For the Repo Overall¶
Top-level README explains both artifacts and links to the blog post
GitHub Actions CI runs correctness tests on every push (JMH is opt-in, not on every push)
Blog post published on Hashnode with 30+ views within 30 days of publication (this rung’s audience threshold is higher than Rung 4’s)
Repo has a
LEARNINGS.mddocumenting three things that surprised you while building this
Signal It Sends¶
You understand concurrency, not just concurrent APIs. Anyone can call
synchronized. This rung proves you can reason about contention, atomicity, memory visibility, and thread scheduling.You measure. JMH results in the README say “I know that
System.nanoTime()is a lie for microbenchmarks.” That’s a career-marker.You’ve internalized virtual threads. This is the Java-21 feature every senior study partner will ask about in 2027. Having a benchmark under your belt means you answer from evidence, not vibes.
You can write a rate limiter. Rate limiters are the archetypal “system design lite” whiteboard problem at product companies. Having shipped one, tested under contention, is a direct study-signal.
You’ve done a hard thing publicly. This is the first rung where the difficulty of the work is visible to a casual observer.
Common Failure Modes — Read This Twice¶
Skipping JMH. You “benchmark” with a for-loop and
System.nanoTime(). Every senior Java engineer who looks at your repo will immediately downgrade you. JMH is not optional for this rung.Not testing under contention. You write a rate limiter, run one thread through it, declare victory. This rung is specifically about contention. If your tests don’t have 100+ threads hammering the code, you haven’t tested it.
Under-scoping to only virtual threads. Virtual threads are trendy but shallow. If Rung 5 is just “I wrote a virtual thread demo,” it doesn’t hit the senior bar. The rate limiter is what makes this rung heavy.
Ignoring the pinning problem. Virtual threads pin to their carrier under
synchronizedblocks and certain native calls. If your benchmark usessynchronized(through library dependencies, JDBC drivers, etc.) and you don’t detect the pinning, your numbers are wrong and you’ll be embarrassed when someone catches it.Publishing benchmark results without hardware/JDK details. Meaningless. Every JMH chart needs: JDK build, GC used, heap size, CPU model, core count, whether HT was on. Without these, the numbers are noise.
Slipping the deadline. This is the hard gate. If M7 arrives and you haven’t started, cut something else, not this. See
09_hard_gates.md.
Time Estimate¶
Rate limiter — three algorithms: ~15 hours
Rate limiter — contention tests + Lincheck: ~8 hours
Rate limiter — JMH benchmarks + writeup: ~6 hours
Mock HTTP server + client harness: ~6 hours
Virtual thread benchmark — runs, tuning, pinning diagnosis: ~10 hours
Chart generation + CSV wrangling: ~3 hours
Blog post (this one is important — budget generously): ~14 hours
Readme, LEARNINGS, polish, CI: ~5 hours
Total: ~67 hours over 8 weeks (~8-9 hours/week)
This is the second-most-expensive rung after Rung 8. It is not a stretch. Plan for it in December-January so you have full M7 and M8 to execute.
Prerequisites¶
All files in
05_concurrency_multithreading/read and worked throughComfort with
java.util.concurrent:ExecutorService,CompletableFuture,AtomicLong,ReentrantLock,StampedLock,LongAdderUnderstanding of the Java Memory Model at a working level (happens-before,
volatilesemantics)Rungs 1-4 all shipped. If Rung 4 is unfinished, you’re already off-schedule for Rung 5.
JMH tutorial completed end-to-end at least once (the
samplemodule of JMH is enough)Read: “State of Loom” and Ron Pressler’s virtual-threads talks — 3 hours of reading before you write a line of Rung 5 code
Stretch Goals (Only If You Ship Before Feb 15, 2027)¶
Add a fourth algorithm: distributed rate limiting using Redis. This is a preview of Rung 7’s Spring Boot service. Even a rough implementation demonstrates the distinction between in-process and distributed rate limiting.
Test the rate limiter with jcstress for race conditions. This is above-and-beyond and every JVM engineer who reads your repo will notice.
Contribute a documentation improvement to a real OSS project using virtual threads — a Spring, Micronaut, or Vert.x doc patch showing correct virtual-thread usage. First OSS contribution: this is often the missing tick-box on senior applications.
Why This Rung Is A Hard Gate¶
Rungs 1-4 are cumulative but recoverable — you can compress or trim them if a personal emergency hits. Rung 5 cannot be recovered later. Here’s the causal chain:
Rung 6 (JVM performance) uses concurrent code as its profiling target. If you don’t have a nontrivial concurrent artifact by M9, you have nothing meaningful to profile.
Rung 7 (URL shortener) needs a rate limiter as a component. If Rung 5 hasn’t produced one, Rung 7 either delivers less or slips into M12.
Rung 8 (ML capstone) is fundamentally an asynchronous, load-tested service. You cannot build it if you haven’t already built and stress-tested one concurrent artifact.
The M13 pitch (“services that survive the next 10 years”) is unsupportable without concurrency evidence. Concurrency bugs are 40% of what “surviving” means in production Java.
If you find yourself in M7 without having started Rung 5: stop Rung 4 immediately even if the blog post isn’t polished. Pin what you have, move on. Rung 5 is more valuable than a polished Rung 4.
Return to README.md · Next: 06_rung6_jvm_performance_case_study.md