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.md for 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() vs Executors.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 slots

  • Blog: 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 RateLimiter interface

  • Each 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 synchronized blocks where an AtomicLong / LongAdder / StampedLock would be more appropriate — and the README explains each choice

  • JMH 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/ folder

  • Chart rendered and checked in as results/latency-vs-concurrency.png

  • run.sh reproduces 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.md documenting 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 synchronized blocks and certain native calls. If your benchmark uses synchronized (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 through

  • Comfort with java.util.concurrent: ExecutorService, CompletableFuture, AtomicLong, ReentrantLock, StampedLock, LongAdder

  • Understanding of the Java Memory Model at a working level (happens-before, volatile semantics)

  • 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 sample module 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)

  1. 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.

  2. Test the rate limiter with jcstress for race conditions. This is above-and-beyond and every JVM engineer who reads your repo will notice.

  3. 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