warming up your workspace

Concurrency in Java

Threads are where most programs go wrong. This track builds concurrency from the ground up in Java: run code on threads and join the results, see exactly how a race corrupts shared state, fix it with synchronized blocks and locks, go lock-free with atomics and compare-and-set, reason about the Java Memory Model and happens-before, manage work with executors and thread pools, coordinate with latches, barriers, and semaphores, build your own thread-safe queue and map, recognise deadlock and livelock before they bite, and finish by building a concurrent job system end to end. The hardest part of real software, made concrete.

10 projects, 250 hands-on levels, run in your browser.

Syllabus

  • Threads & Runnables: A thread is a second line of execution running inside your program. This project covers the mechanics: starting a thread to run a piece of code, waiting for it to finish with join, the difference between run and start, interrupting and sleeping, and getting results back out. It ends by splitting a real computation across several threads and combining the parts, the shape of every parallel algorithm that follows. Because each thread here writes to its own slot and we always join before reading, the results are fully deterministic, no synchronization needed yet.
  • Shared State & Races: When two threads touch the same data, the trouble starts. This project makes that trouble concrete and, crucially, deterministic, by modeling interleavings as data rather than gambling on a real race to misbehave. You will simulate the read-modify-write steps behind a lost update, see why an increment must be atomic, reason about visibility as a happens-before graph, model mutual exclusion as critical-section occupancy, and watch a bank's money vanish under a bad interleaving and stay put under a lock. Understand the failure here and the fixes in the next projects will make sense.
  • Synchronization: Now the fixes, with real threads. A synchronized block or lock makes a critical section run by one thread at a time, so a counter incremented by eight threads lands on exactly the right total, every run. This project builds thread-safe code you can trust: synchronized methods and a shared counter and id generator, the explicit ReentrantLock, guarded blocks with wait and notify and a bounded buffer built by hand, a bank whose balances stay correct under contention, and a thread-safe accumulator. Every test starts many threads, joins them, and checks a settled invariant, the only way to test concurrency without flakiness.
  • Atomics & CAS: Locks are not the only way to be safe. The java.util.concurrent.atomic classes offer variables whose operations are indivisible without any locking, built on the hardware's compare-and-set instruction. This project uses atomic counters that reach their exact total under contention, the CAS retry loop that makes a lock-free increment, an AtomicReference holding the head of a Treiber lock-free stack, functional updates and the high-throughput LongAdder, and a lock-free accumulator. No locks held, no threads blocked, and still perfectly correct.
  • The Java Memory Model: A race is not only about interleaving; it is about whether one thread can see another's writes at all. The Java Memory Model defines that with happens-before: if one action happens-before another, the first's effects are visible to the second. This project models the relation and its rules, thread start and join, monitor release and acquire, then shows volatile establishing visibility (including a real handoff between threads), what safe publication means, why final fields and immutability make sharing safe, and how double-checked locking goes wrong without volatile and right with it.
  • Executors & Thread Pools: Creating a thread per task does not scale: thread creation is expensive and unbounded threads exhaust the machine. An executor decouples submitting work from running it, handing tasks to a managed pool of worker threads. This project uses ExecutorService to submit Runnables and Callables, collects results through Futures and invokeAll, then builds a fixed thread pool from scratch out of a blocking task queue and worker threads, and finishes with a parallel task engine. Every result is read through get or after awaitTermination, so the tests are exact.
  • Coordination & Synchronizers: Beyond mutual exclusion, threads need to coordinate: wait for each other, gather at a barrier, limit how many run at once. The java.util.concurrent synchronizers package these patterns. This project builds guarded blocks with wait and notify, counts workers down to a finish line with CountDownLatch, gathers parties at a CyclicBarrier, bounds concurrency with a Semaphore, and runs producers and consumers over a BlockingQueue. Each test waits for the coordinated state to settle, then checks it, so the results are exact.
  • Concurrent Data Structures: A plain HashMap or ArrayList corrupts under concurrent writes. This project builds thread-safe collections and uses the JDK's: a synchronized stack and queue, a map sharded into independently-locked stripes for better parallelism, the lock-free ConcurrentHashMap with its atomic merge and compute, and the read-optimized CopyOnWriteArrayList. It ends with a concurrent word-count engine that tallies a stream of words across many threads and lands on the exact totals.
  • Hazards & Patterns: Even correct locking can deadlock if two threads grab the same locks in opposite orders. This project makes the hazards concrete: deadlock as a cycle in a wait-for graph, livelock and starvation, and the patterns that prevent them, acquiring locks in a global order, confining state to one thread, and handing off immutable snapshots instead of sharing mutable state. The failure modes are modeled deterministically; the fixes run on real threads and finish every time.
  • Capstone: A Concurrent Job System: The finale assembles everything into one working system: a job system that takes a batch of jobs, spreads them across a pool of worker threads pulling from a shared blocking queue, collects the results safely, and reports aggregate statistics, exactly, every run. You will build the job and result model, the blocking queue, the worker pool with clean shutdown, the result collection, and finally the complete JobSystem class. By the end you have a small but real concurrent engine, built from threads, queues, atomics, and the safe patterns of the whole track.

Key concepts

  • Atomic variable: A java.util.concurrent.atomic class (AtomicInteger, AtomicLong, AtomicReference) whose operations are indivisible without locking, built on compare-and-set.
  • Atomicity: An operation is atomic when it appears to happen all at once, never half-done as seen by other threads. count++ is not atomic; AtomicInteger.incrementAndGet is.
  • BlockingQueue: A thread-safe queue whose put blocks when full and take blocks when empty, handling all the waiting for producer-consumer. ArrayBlockingQueue is bounded; Linke…
  • Bounded buffer: A fixed-capacity queue where put blocks when full and take blocks when empty, the heart of producer-consumer. Built from wait/notify or provided as ArrayBlocki…
  • Callable: Like Runnable but it returns a value and may throw a checked exception. Submitting one to an executor yields a Future for its result.
  • CAS retry loop: Building a read-modify-write atomically: read the value, compute the next, and CAS it in; if the CAS fails because another thread changed it, loop and retry. H…
  • Coffman conditions: The four conditions all required for deadlock: mutual exclusion, hold-and-wait, no preemption, and circular wait. Break any one and deadlock is impossible.
  • Compare-and-set (CAS): An atomic hardware operation that writes a new value only if the current value equals an expected one, reporting success in a single indivisible step. The foun…
  • Concurrency: Multiple computations making progress during overlapping time periods. On multiple cores they run in parallel; on one core they interleave. The source of both…
  • Concurrent collection: A collection designed for safe concurrent access (ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList), unlike a plain HashMap or ArrayList which co…
  • ConcurrentHashMap: The JDK thread-safe hash map, allowing concurrent reads and writes with fine-grained locking. Its merge and computeIfAbsent update a key atomically.
  • Context switch: The scheduler saving one thread state and loading another so they share a core. Frequent switches (from heavy blocking or too many threads) cost performance.
  • Cooperative cancellation: Stopping work by checking an interrupt flag (or a volatile boolean) and returning, rather than forcibly killing a thread, which Java does not allow because it…
  • CopyOnWriteArrayList: A list that copies its backing array on every write, making reads lock-free and always consistent. Best when reads vastly outnumber writes; iterators see a sta…
  • CountDownLatch: A one-shot gate starting at a count: threads countDown, and await blocks until it reaches zero. Used to wait for N workers or as a start signal. It does not re…
  • Critical section: A region of code that accesses shared state and must be run by only one thread at a time, protected by a lock.
  • CyclicBarrier: Gathers a fixed number of parties: each awaits until all arrive, then all proceed, and it resets for reuse. Ideal for step-locked simulations.
  • Daemon thread: A background thread that does not prevent the JVM from exiting; when only daemon threads remain, the program ends. Set with setDaemon(true) before start.
  • Data race: Two threads access the same variable with no happens-before ordering and at least one writes. The memory model leaves the outcome undefined.
  • Deadlock: A cycle of waiting where each thread holds a lock the next one needs, so none can proceed. It corresponds to a cycle in the wait-for graph and needs all four C…
  • Defensive copy: Copying a mutable input on the way in and a mutable field on the way out, so an immutable class internal state cannot be changed by callers.
  • Double-checked locking: A lazy-initialization idiom that checks the field, locks, then checks again before creating. It is correct only when the instance field is volatile, otherwise…
  • Executor: An object that decouples submitting a task from running it. ExecutorService manages a pool of worker threads, accepts Runnables and Callables, and can be shut…
  • Fairness: A fair lock grants itself to waiting threads in arrival order, preventing starvation at some cost to throughput. ReentrantLock(true) is fair.
  • Final field: A field set once in the constructor. The memory model guarantees its correct value is visible to any thread after construction, even through a plain reference,…
  • Fork-join: A framework that recursively splits a task into subtasks run in parallel and joins their results, with work-stealing across worker threads. The model behind pa…
  • Future: A handle to a result that is not ready yet. get() blocks until the task completes and returns its value; isDone reports completion. What makes collecting concu…
  • Guarded block: Code that waits until a condition holds before proceeding, built with a while-loop, wait, and notifyAll. The basis of bounded buffers and hand-built synchroniz…
  • Happens-before: The partial order at the core of the memory model: if action x happens-before y, x effects are visible to y. It comes from program order plus synchronization e…
  • Holder idiom: Lazy initialization using a static holder class: the JVM initializes a class on first use, thread-safely, so the value is built once with no explicit locking.
  • Immutability: An object that never changes after construction (all fields final, no setters, no leaked mutable internals) can be shared by any number of threads with no sync…
  • Interrupt: A cooperative request for a thread to stop. It sets the thread interrupt flag (and wakes blocking calls with InterruptedException); well-behaved code checks th…
  • Intrinsic lock: The built-in lock that every Java object carries, used by synchronized. Also called the monitor lock.
  • Java Memory Model: The specification of when one thread writes become visible to another, defined through the happens-before relation. It is what makes concurrent Java reasoning…
  • join: Blocks the calling thread until the target thread finishes. It also establishes happens-before, so everything the joined thread did becomes visible afterward.
  • Lazy initialization: Creating a value only on first use rather than up front. Doing it safely under concurrency needs double-checked locking or the holder idiom.
  • Livelock: Threads stay active but make no progress, each repeatedly reacting to the other, like two people stepping aside in a hallway forever. Randomized backoff breaks…
  • Lock: A mechanism that grants exclusive access to a critical section. Java offers intrinsic locks (synchronized) and explicit locks (ReentrantLock).
  • Lock contention: When threads compete for the same lock and block waiting. High contention serializes work and hurts scalability; finer-grained locking or lock-free designs red…
  • Lock ordering: Acquiring locks in a fixed global order (for example by id, lowest first) so no circular wait can form. The standard deadlock prevention.
  • Lock striping: Splitting a structure into segments each with its own lock, so operations on different segments do not block each other. The idea behind a scalable concurrent…
  • Lock-free: An algorithm where threads never block each other, made of atomic operations and retry loops. No deadlock, and a stalled thread cannot hold up the rest. The Tr…
  • LongAdder: A high-throughput counter that spreads increments across internal cells and sums them on read, beating a single AtomicLong under heavy contention.
  • Lost update: When two threads both read a value, both modify it, and both write back, one update overwrites the other. The symptom that count++ is not atomic.
  • Monitor: Every Java object has an associated monitor (intrinsic lock) plus a wait set. synchronized takes the monitor; wait and notify operate on it.
  • Mutual exclusion: The property that at most one thread is in the critical section at a time. The job of a lock or synchronized block.
  • Parallelism: Running computations literally at the same time on multiple processors. Parallelism is one way to achieve concurrency, but concurrency is also about structure,…
  • Permit: A unit of a Semaphore capacity. Acquiring one allows a thread into the limited section; releasing it lets another in.
  • Producer-consumer: A pattern where producer threads put work into a queue and consumer threads take it out, decoupling their rates. A poison value signals consumers to stop.
  • Race condition: A bug where the result depends on the unpredictable timing of threads accessing shared state. The classic is a lost update from a non-atomic increment.
  • Reentrancy: A lock is reentrant when the thread already holding it can acquire it again without deadlocking; it is released only when the hold count returns to zero. Java…
  • ReentrantLock: An explicit lock you lock() and unlock() yourself (always in try-finally), with extras over synchronized: tryLock, an optional fairness policy, and an inspecta…
  • Runnable: A task with no return value: an object (often a lambda) whose run method holds the code a thread executes.
  • Safe publication: Making an object reference visible to other threads so they see it fully constructed: via a static initializer, a final field, a volatile field, or a lock. A p…
  • Semaphore: Holds a number of permits; acquire takes one (waiting if none remain) and release returns one. Bounds how many threads may proceed at once, throttling a scarce…
  • Snapshot iterator: An iterator that traverses the collection as it was when iteration began, immune to concurrent modification. CopyOnWriteArrayList provides one.
  • start versus run: Calling a thread start() launches a new thread that runs its task; calling run() directly just executes the code on the current thread, with no concurrency. Co…
  • Starvation: A thread is perpetually denied the resource it needs, passed over forever under an unfair scheduler. A fair lock prevents it.
  • synchronized: A keyword that acquires an object intrinsic lock for a block or method, giving mutual exclusion and establishing happens-before between release and the next ac…
  • Synchronizer: A high-level coordination object from java.util.concurrent (latch, barrier, semaphore, phaser) that packages a wait/notify pattern threads use to coordinate.
  • Thread: An independent line of execution within a process, with its own call stack but sharing the process heap. Created by giving a Thread a Runnable and calling star…
  • Thread confinement: Keeping state accessible to only one thread, so no synchronization is needed. Stack confinement (locals) and ThreadLocal are the common forms.
  • Thread pool: A managed set of worker threads that pull tasks from a queue and run them, reusing threads instead of creating one per task. Created with Executors.newFixedThr…
  • ThreadLocal: A variable that gives each thread its own independent copy, so threads never interfere. A clean way to confine mutable state per thread.
  • Treiber stack: A lock-free stack: push and pop swap the head reference with compare-and-set, retrying on failure. The textbook lock-free data structure.
  • Visibility: Whether a write made by one thread can be seen by another. Guaranteed only across a happens-before edge; without one, a reader can see a stale cached value.
  • volatile: A field modifier guaranteeing visibility: a write is seen by later reads, which always return the most recent value. It establishes happens-before for that fie…
  • wait and notify: On an object monitor, wait() releases the lock and sleeps until another thread calls notify or notifyAll. Always wait in a loop that rechecks the condition. Th…
  • Wait-for graph: A directed graph where an edge from thread A to B means A is blocked waiting for a lock B holds. A cycle in it is exactly a deadlock.
  • Work stealing: An idle worker thread takes (steals) tasks from a busy worker queue, balancing load automatically. Used by the fork-join pool.