
A timeline-driven story of how single-core clock speeds stalled in the mid-2000s as Dennard scaling broke down, ending the era when programs got faster for free with each new chip. You'll see how the industry pivoted to multicore, and why that hardware shift forced ordinary developers to learn concurrency to use the cores they were now paying for. This frames the whole course as learning to spend those cores safely.
You'll write and run threads three ways: subclassing Thread, implementing Runnable, and passing a lambda — built around a busy restaurant kitchen with a saute cook, a prep cook, and a garnish one-liner. You'll see start() versus calling run() directly, and use join() to wait for a thread to finish. Thread names printed in the output let you watch real interleaving as several threads run side by side.
You'll build a runnable example where many threads increment a shared XP counter without coordination, then run it to watch the final total come out wrong and different every run. The bug is made concrete and reproducible — including a "boss fight" where hits never land — so the rest of the section has a real problem to solve, and you'll watch the same code produce a different answer every single time.
You'll fix the broken counter with synchronized methods and synchronized blocks, learning about intrinsic locks and the object monitor every Java object carries. Running the corrected version — a goblin gold glitch and a guild treasury heist — gives you a now-consistent total. You'll also weigh the trade-off: correctness at the cost of serialized, one-lane access.
You'll watch a thread that never sees another thread's update to a boolean stop flag — a rover that will not halt — then fix it with volatile to guarantee visibility. You'll see clearly that volatile solves visibility but not atomicity: a volatile count++ is still a race. Both the broken and fixed versions run so you can compare them.
You'll replace synchronized counting with AtomicInteger and incrementAndGet, then see compareAndSet drive a small retry loop. You'll learn how atomics use a hardware compare-and-swap to stay correct without locks. Running a multi-threaded count — four heroes on one perfect counter, plus twin atomics in a dungeon raid — confirms the total is exact.
You'll untangle two ideas that sound identical but are not: concurrency is about structuring a program as independent tasks that can make progress, while parallelism is about actually running them at the same instant on multiple cores. A coffee-shop analogy makes it stick — one barista juggling many orders is concurrency, several baristas working at once is parallelism. You'll see that you can have one without the other.
You'll meet the Lock interface, learn to lock and unlock in a finally block, and use tryLock with a timeout to avoid waiting forever — all in a detective's evidence room. You'll contrast it with synchronized and see what explicit locks buy you. An example shows tryLock letting a thread back off gracefully instead of blocking indefinitely.
You'll use a Condition obtained from a ReentrantLock to make threads wait for and signal state changes, building a tiny bounded buffer — a "loot buffer" where a boss producer waits when full and a hero consumer waits when empty. Running it shows clean hand-offs between producer and consumer, and you'll learn why you always wait inside a while loop, never a bare if.
You'll use a CountDownLatch as a one-shot gate: a main thread awaits while several workers each countDown when done, then proceeds once the count hits zero. You'll run a server-startup coordination example and a "three runners, one whistle" group start. You'll also contrast it with join() for cases where you do not hold the Thread references, and see that a latch cannot be reset.
You'll use a Semaphore with a fixed number of permits to cap how many threads touch a scarce resource at once — a bouncer with a limited set of wristbands, modelling a connection pool. Running more workers than permits (five heroes, two slots) lets you watch them queue for an opening. You'll see acquire and release wrapped safely so permits are never leaked.
You'll use a CyclicBarrier to make a set of threads rendezvous at the end of each phase before any starts the next, modelled as musicians syncing up across three movements of a piece. Running it shows all threads syncing repeatedly, and you'll attach a barrier action that fires on each arrival. You'll also contrast it with CountDownLatch, which fires only once.
You'll learn why the genuinely hard part of concurrency is not creating threads but coordinating access to the data they share. You'll see how two threads reading and writing the same variable can interleave in ways that corrupt it, and why these bugs are intermittent, timing-dependent, and brutal to reproduce. The takeaway is a mental frame: every shared mutable value is a hazard until proven otherwise.
You'll replace hand-managed threads with an ExecutorService, submitting tasks and shutting the pool down cleanly. You'll learn why reusing a pool beats creating a thread per task. Running a batch through a fixed pool — a boss raid where three workers chew through twelve monsters — lets you observe reuse via thread names as a handful of workers handle many jobs.
You'll submit Callable tasks that return values and collect results through Future.get, including a get with a timeout. You'll see how an exception thrown inside a task surfaces at get as an ExecutionException. Running a small parallel computation — packing and dispatching orders, then gathering all the answers — shows the full submit-and-collect cycle.
You'll compare the Executors factory pools — fixed, cached, single-thread, and scheduled — and when each fits, framed as raid parties, on-demand spawning, an ordered quest log, and timed potion brewing. You'll run a ScheduledExecutorService to fire a task after a delay and again at a fixed rate. You'll also see the dangers of the unbounded cached pool under load.
You'll walk the full lifecycle: shutdown versus shutdownNow, awaitTermination with a timeout, and what happens to queued tasks. You'll see how catching and logging exceptions inside tasks keeps a failure from vanishing silently. A pool that drains gracefully on exit ties it together.
You'll see why a CPU-bound pool wants about one thread per core while an IO-bound pool can be much larger, and watch a starvation deadlock arise when dependent tasks share too small a pool. You'll ask Java how many cores you have, then run both a healthy and a starved configuration. You'll leave with a practical rule of thumb for sizing, including separate pools for dependent tasks.
You'll demystify the Java Memory Model without the jargon: each thread can cache values, the compiler and CPU can reorder instructions, and without explicit coordination one thread's writes may never become visible to another. You'll meet visibility, atomicity, and ordering as three separate problems. This sets up happens-before as the rule that tames them, to be paid off in the final section.
You'll kick off asynchronous work with supplyAsync and transform its result with thenApply, all without blocking the calling thread, in a quest-reward pipeline. Printed timestamps prove the work is happening off the main thread. You'll contrast the style with blocking Future.get.
You'll use thenCompose to chain a second async call that depends on the first result, and thenCombine to merge two independent async results — finding a hero, then rolling and combining damage. One example fetches then enriches; another runs two lookups in parallel and joins them. You'll make the dependent-versus-independent distinction concrete and see why thenCompose avoids the nested-future trap.
You'll launch many CompletableFutures at once and wait for all of them with allOf, then collect every result; anyOf lets you react to whichever finishes first — looting boss drops and racing servers to the first answer. You'll run a parallel fan-out over a whole raiding party. You'll also see how to gather results after allOf completes.
You'll recover from failures inside an async pipeline with exceptionally, inspect both result and error with handle, and observe completion with whenComplete. Running a chain where one stage throws shows the pipeline recovering. You'll see why async exceptions do not propagate like synchronous ones — a plain try/catch will not save you.
You'll run CompletableFuture stages on a custom executor instead of the common pool, and assemble an end-to-end pipeline that fetches, transforms, and combines several async sources using your own named pools. Running the whole pipeline prints the composed result. You'll learn why the default common pool can be the wrong place for blocking work.
A numbers-driven look at why throwing more threads at a problem hits a wall. You'll meet Amdahl's law and the serial fraction that caps your speedup, then layer on the real-world taxes: lock contention, context-switch cost, and cache effects. You'll build the intuition that coordination is never free and sometimes costs more than it saves.
You'll use ConcurrentHashMap from many threads and contrast it with a synchronized HashMap, then use atomic operations like compute to update entries safely without external locks — a counter that lies versus three wizards tallying spell casts correctly. Running a concurrent counting example across several threads makes the difference visible. You'll see why a compound check-then-act on a plain map is still a race.
You'll build the classic producer-consumer with a BlockingQueue, where producers put and consumers take, and the queue handles all the waiting for you. You'll run several producers and consumers together. You'll see how the bounded queue provides natural backpressure, and shut it down cleanly with a poison-pill marker.
You'll turn a sequential stream into a parallel one and measure the speedup on a CPU-bound aggregation, then watch a shared mutable accumulator — a single shared pile of gold — break correctness. You'll run both the good case and the broken one, then fix it with a stateless reduction. You'll leave with clear guidance on when parallel streams actually pay off.
You'll implement a RecursiveTask that splits a large array sum into subtasks — tallying a guild's XP — forking and joining them, and learn how the work-stealing pool keeps cores busy. Running it against a sequential sum shows the payoff, and you'll see why the cutoff size matters. You'll learn when divide-and-conquer is the right shape.
You'll create virtual threads with Thread.ofVirtual and an executor that spawns one per task, launching thousands cheaply where platform threads would exhaust memory — ten thousand adventurers at once. A high-fan-out example puts it through its paces. You'll learn how virtual threads make simple blocking code scale, why you should never pool them, and get a peek at structured concurrency.
A labelled overview of the layers you have learned, from lowest to highest: raw Thread and synchronized, then java.util.concurrent locks and atomics, then ExecutorService and thread pools, then CompletableFuture for async composition, and finally virtual threads from Project Loom. You'll come away with a mental map of the whole toolbox, what to reach for first in new code, and why modern code lives near the top of this stack.
You'll pay off the memory-model promise from the opener: happens-before is the ordering guarantee that lets you reason about visibility, and synchronized, volatile, locks, and thread start/join all establish it. A clear before-and-after picture of two threads, plus a four-step way to reason about a shared variable, makes it concrete. You'll leave with a rule you can actually apply.
You'll tour the classic ways concurrent programs hang: deadlock from circular lock ordering, livelock from threads endlessly yielding to each other, and starvation when some threads never get a turn. The dining philosophers make deadlock vivid, and you'll get the lock-ordering rule that prevents it. You'll learn to diagnose each from its symptoms, including reading it from a thread dump.
You'll see how two threads updating unrelated fields that happen to share a CPU cache line can silently cripple performance, and why understanding the hardware — cache lines, the memory hierarchy — makes you a better concurrent programmer. You'll walk through how false sharing actually happens between two counters on one cache line. You'll meet the idea of designing with the machine, not against it.
You'll confront why concurrency bugs vanish when you look at them and how professionals fight back: thread dumps and jstack, logging with thread context, stress testing, and race detectors. You'll walk through reading a deadlock from a thread dump and a practical hang playbook. You'll finish with the tools ranked by when to reach for them when a system mysteriously hangs.
You'll close with where Java concurrency is heading: virtual threads reshaping how we write servers, structured concurrency making task lifetimes explicit and cancellable, and scoped values replacing thread-locals. You'll place these on a timeline of Java's concurrency evolution. You'll leave excited about writing simple code that scales.
This course contains the use of artificial intelligence.
Java concurrency is the skill that separates developers who can use modern multicore hardware from those who quietly leave most of their CPU idle. This course teaches it the way it actually clicks: by weaving short conceptual lectures together with hands-on code you can run yourself. Each coding section opens with a single plain-English concept lecture that frames the problem, and then every idea after it is something you build, run, and watch behave on real threads.
You will start at the metal: creating threads, watching a shared counter corrupt itself in a live race condition, and then fixing it with synchronized, volatile, and atomic variables. From there you move up the stack to the java.util.concurrent toolkit — explicit locks, condition variables, latches, semaphores, and barriers — and then to executors and thread pools, where you stop hand-managing threads and start submitting tasks. The async chapters take you through CompletableFuture composition (chaining, fan-out, fan-in, and error handling), and the final stretch covers the modern toolkit: concurrent collections, the producer-consumer pattern, parallel streams, fork/join, and virtual threads from Project Loom.
The course is structured as five sections, each anchored by runnable examples so the theory always has somewhere to land. Concepts and code are interleaved deliberately: you meet a hazard, you see it bite, and then you neutralize it with the right tool. Code lectures use vivid, memorable examples — RPG raids, busy kitchens, detective evidence rooms, and more — to keep the mechanics concrete while the underlying Java APIs stay exactly what you would use in production.
The closing lectures pull everything together at a conceptual level. The final section ends with a run of deeper conceptual lectures that every serious concurrent programmer needs: a map of the full concurrency toolbox, the happens-before rule that makes reasoning about visibility possible, the classic failure modes (deadlock, livelock, and starvation), false sharing and mechanical sympathy, a practical playbook for debugging Heisenbugs, and a look at where Java concurrency is heading with virtual threads, structured concurrency, and scoped values. You finish able to write concurrent Java that is correct, fast, and genuinely understandable.