Everything the adaptive question bank can teach and test in Concurrency, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
A. Concurrency, parallelism and units of execution
•
concurrency is interleaving independent tasks; parallelism is running them in the same instant on separate cores
•
processes get separate address spaces; threads of one process share the heap, so only threads can race on it
•
each thread owns a stack, registers and a program counter; everything reachable from the heap is shared
•
a switch saves state, crosses the kernel and cools the caches, which is why a thread per request stops scaling
•
goroutines, virtual threads and fibers are scheduled by the runtime onto a small pool of OS threads, with no syscall per switch
•
a goroutine starts with a few kilobytes of growable stack, so a million of them is routine where a million OS threads is not
•
a blocking call parks a virtual thread and returns its carrier OS thread to run something else
•
a cooperative scheduler advances only at yield points, so one non-yielding task freezes every task sharing that thread
•
the serial fraction caps the speedup: 5% serial means at most 20x, no matter how many cores you add
B. Shared state, race conditions and data races
•
the unit that races is one scalar object or a maximal run of adjacent bit-fields, not a whole struct
•
two threads access the same location, at least one of them writes, and no happens-before edge orders the pair
•
a race condition is a timing-dependent wrong result; a data race is an unordered access — a lock-protected check-then-act is the first without the second
•
the condition you tested can already be false when you act on it, even if the test and the action are each atomic
•
x++ is a load, an add and a store, so two interleaved increments can add one
•
the region that at most one thread may execute at a time for the invariant to hold at its exit
•
in C++ a data race makes the whole program undefined, which licenses the compiler to assume it never happens
•
Go's implementation restriction confines the damage to the words involved: a racy Go program is wrong but stays memory-safe
•
a compiler may reload, split or duplicate a racy read, so "it is only a stale counter" is not a defense
•
writing one field must never disturb an adjacent one, which is exactly what makes per-field locking legal
C. Atomicity, visibility and ordering
•
thread safety needs atomicity, visibility and ordering; a lock supplies all three, an atomic variable only some
•
no other thread can observe the operation half-completed
•
a write is not automatically observable elsewhere; some synchronizing operation has to make it so
•
the order one thread's operations appear to take, seen from another thread, need not be its program order
•
the optimizer may hoist, sink, fuse or delete accesses; the as-if rule preserves the single-threaded result and nothing else
•
store buffers, speculation and cache protocols reorder at run time, independently of the compiler
•
a non-volatile long or double write may be split in two, letting a reader see one half of each of two writes
•
volatile fixes visibility and ordering but not atomicity, so v++ is still a lost-update race
•
in C and C++, volatile means memory-mapped I/O; it orders nothing between threads and is not atomic
•
a loop reading a non-atomic stop flag may have the load hoisted into a register and spin forever
D. Happens-before and what a memory model promises
•
it states exactly which writes a read may observe, so compiler, CPU and programmer can agree
•
happens-before orders only some pairs of operations, and the unordered pairs are where every bug lives
•
a read may see any write it does not happen-after, unless another write is ordered between them
•
program order orders one thread's own operations and says nothing across threads
•
edges chain, which is how a two-hop handoff through a third thread stays safe
•
the cross-thread edge is created by a release operation and the acquire that reads what it wrote
•
a data-race-free program can be reasoned about as a simple interleaving; a racy one cannot be reasoned about at all
•
one global order of all operations, consistent with each thread's program order
•
with no edge the model never promises the value arrives "soon"; a stale read may persist indefinitely
•
the JMM forbids values that exist only because a speculative read justified the write that produced it
E. The Go memory model's synchronization edges
•
the go statement happens-before the new goroutine's first instruction, so its arguments are safely visible
•
a goroutine's exit is ordered with nothing; you must join with a channel, a WaitGroup or a lock
•
a send on a channel happens-before the corresponding receive finishes
•
on an unbuffered channel the receive happens-before the send returns, which is why it synchronizes in both directions
•
the k-th receive happens-before the (k+C)-th send completes, which turns a capacity-C channel into a semaphore
•
closing a channel happens-before a receive that returns because the channel is closed
•
for any n, the n-th Unlock happens-before the (n+1)-th Lock returns
•
an RUnlock happens-before the next Lock returns, and a writer's Unlock before later RLock calls return
•
the single completed call inside Once.Do(f) happens-before every Do returns, in every goroutine
•
the Done calls happen-before the Wait they release returns
•
sync/atomic behaves like C++ seq_cst atomics; Go deliberately offers no relaxed or acquire-only variants
F. Java's memory model and threading contract
•
an unlock of a monitor happens-before every subsequent lock of that same monitor, and only that monitor
•
a synchronized instance method locks this; a synchronized static method locks the Class object, so the two do not exclude each other
•
a write to a volatile field happens-before every later read of that field
•
declaring an array volatile makes the reference volatile, never the elements
•
Thread.start() happens-before the thread's first action, and its last action happens-before a successful join()
•
writes to final fields in the constructor are visible without synchronization to any thread that reads a properly constructed reference
•
leaking this from a constructor, by starting a thread or registering a listener, voids that guarantee
•
the JVM serializes class initialization, which is why a holder class gives lazy singletons with no explicit lock
•
interrupt() sets a flag or raises InterruptedException at a blocking point; it never stops running code
•
asynchronously killing a thread releases its locks with invariants half-updated, which is why Thread.stop is gone
G. C++ memory_order and fences
•
all seq_cst operations appear in one total order every thread agrees on, and seq_cst is what you get when no order argument is written
•
atomic and coherent, but it orders no other access, so it cannot publish the data it flags
•
nothing sequenced before the release may be reordered after it
•
nothing sequenced after the acquire may be reordered before it
•
the edge exists only when the acquire actually reads the value that release wrote; a release nobody reads orders nothing
•
a read-modify-write that acquires the value it read and releases the value it wrote
•
later read-modify-writes on the same atomic extend the release, so an acquirer that reads any of them still gets the edge
•
every atomic object has one total order of its own writes, respected even under relaxed, and no thread may see it run backwards
•
dependency ordering is specified but every implementation strengthens it to acquire
•
atomic_thread_fence orders the accesses around it without being attached to any one object
•
the store-load direction is the only one x86 must actively fence, which is why seq_cst stores are the expensive ones
•
acquire/release are nearly free on x86, so a missing order argument often fails only on ARM or POWER
H. Mutual exclusion and lock design
•
one lock must cover every variable the invariant spans, not one field at a time
•
an unlocked read of guarded state can see the invariant mid-update, or never see the update at all
•
release in finally, defer or an RAII guard so an early return or a throw cannot strand the lock
•
synchronized is block-scoped and uninterruptible; ReentrantLock adds tryLock, timeouts, interruptibility and a fairness option
•
a reentrant lock counts holds per owner, so a method can safely call another method that takes the same lock
•
sync.Mutex and std::mutex are not reentrant; locking twice on one thread deadlocks or is undefined
•
many concurrent readers or one writer, which only pays off when reads are frequent and long
•
an unbroken stream of readers can block a writer indefinitely unless the lock is write-preferring
•
two readers each trying to upgrade to the write lock deadlock; release, re-acquire, then re-validate what you read
•
spinning wins only when the expected wait is shorter than two context switches, so real mutexes spin briefly and then park in the kernel
I. Deadlock, livelock, starvation and priority
•
mutual exclusion, hold-and-wait, no preemption and circular wait must all hold at once; break any one and deadlock is impossible
•
a single global acquisition order removes the circular wait, and costs nothing at run time
•
transfer(a,b) and transfer(b,a) deadlock unless the two locks are ordered by a stable key such as an account id or an address
•
std::lock and std::scoped_lock take several mutexes with a try-and-back-off algorithm instead of a fixed order
•
threads keep reacting to each other and never progress; lockstep back-off is the classic cause and randomization the classic fix
•
a thread stays runnable but never wins the resource, because barging or priority keeps favoring others
•
a high-priority thread waits on a lock held by a low-priority thread that a medium-priority thread keeps preempting
•
the holder temporarily runs at the highest waiter's priority so it can finish and release
•
calling a callback or foreign code while holding a lock can acquire an unknown second lock in an unknown order
•
Go panics when all goroutines are asleep; a subset that is stuck just hangs silently
J. Condition variables and waiting
•
block until shared state satisfies a predicate, instead of burning a core re-testing it
•
the predicate is shared state, so testing it and changing it must both happen under the same mutex
•
wait releases the mutex and blocks as one indivisible step, which is what makes a wakeup impossible to lose
•
re-test the predicate after every wake; an if instead of a while is a bug
•
a wait may return with no notification at all, and implementations are permitted to do it
•
another thread can consume the condition between the notify and the woken waiter re-acquiring the mutex
•
notify one only when all waiters wait on the same predicate and any single one can consume the event
•
changing the predicate without the mutex, or signalling before anyone waits, drops the event and hangs the waiter
•
Thread.sleep and yield release no monitors, so sleeping inside a critical section blocks everyone
K. Semaphores, latches and barriers
•
a permit counter: acquire blocks while it is zero, release returns a permit
•
any thread may release a permit it never acquired, which is exactly why a semaphore is not a mutex
•
the standard way to bound concurrent use of N connections, files or in-flight requests
•
a countdown latch opens at zero and can never be reset
•
releases when a fixed number of parties arrive, then re-arms for the next round
•
a barrier expecting more parties than will ever arrive hangs every thread already waiting on it
•
Add must run before the goroutine starts; calling it inside the goroutine races with Wait
•
a synchronization barrier makes threads meet; a memory barrier orders memory accesses — one word, unrelated mechanisms
L. Atomics, CAS and non-blocking progress
•
an atomic read-modify-write cannot be split by another thread, so no increment is lost
•
write the new value only if the current one still equals the expected one, and report which happened
•
read, compute, CAS, retry on failure: the shape of almost every lock-free update
•
compare_exchange_weak may fail spuriously and is cheaper on load-linked/store-conditional machines, so it belongs inside a loop
•
the value went A→B→A, so the CAS succeeds even though the state it reasoned about is gone
•
a version tag beside the pointer, a double-width CAS, or deferring reclamation so the node cannot be reused
•
some thread always makes progress, whatever the scheduler does to the others
•
every thread finishes in a bounded number of its own steps, so no thread can be starved
•
progress is guaranteed only for a thread running in isolation, so it needs a contention manager
•
a lock-free structure keeps working when a participant is descheduled or killed; a lock does not
•
speculate, then commit or retry on conflict; its selling point is that transactions compose where locks do not
M. Caches, coherence and the cost of sharing
•
coherence and sharing operate on lines of about 64 bytes, never on individual variables
•
a core must invalidate every other copy of a line before it may write it
•
two unrelated variables in one line make independent threads bounce that line between cores
•
pad and align independently-written objects apart by the destructive-interference size
•
a store is visible to its own core before any other, which is precisely the store-load reordering
•
a locked instruction takes exclusive ownership of the line, so contended atomics serialize on cache traffic
•
per-thread cells summed only on read (LongAdder) trade an expensive read for a scalable write
•
memory attached to another socket is measurably slower, so thread and allocation placement change throughput
•
two hardware threads share one core's execution units, so doubling threads does not double work
N. Concurrent data structures and safe reclamation
•
locking every method makes each call atomic and leaves every multi-call sequence racy
•
putIfAbsent, compute and merge exist because the caller cannot make the two-call form atomic
•
a java.util.concurrent iterator never throws ConcurrentModificationException and may or may not reflect updates made after it was created
•
per-bin locking and CAS insertion are why a concurrent map scales where a globally synchronized one does not
•
lock-free reads paid for by copying the whole array on every write; only for read-mostly data
•
the producer/consumer handoff primitive: blocking take, blocking put, and an optional bound
•
a bounded queue creates backpressure; an unbounded one converts overload into latency and memory growth
•
a reader can still be inside a node the writer has already unlinked, so freeing is the hard part of lock-free design
•
each reader publishes the node it is using, and a node is freed only when no hazard pointer names it
•
readers run with no synchronization at all; the writer publishes a new version and frees the old one after a grace period
•
the writer bumps a counter to odd then even, and readers retry if it changed or was odd, making reads cheap and writes unblocked
O. Thread pools, executors and work stealing
•
creating a platform thread costs a stack and a syscall, usually far more than the task itself
•
roughly one worker per core; extra workers only add switching and cache pressure
•
size by the wait-to-compute ratio, because a blocked worker occupies no core
•
with no bound, excess work becomes latency and memory instead of visible rejection
•
abort, discard, discard-oldest or caller-runs; caller-runs throttles the submitter, which is backpressure for free
•
tasks that wait on other tasks in the same bounded pool can consume every worker and hang
•
a throw inside submit is stored in the Future and is never seen unless someone inspects it
•
stop accepting and drain, or interrupt what is running; either way, then awaitTermination
•
each worker pushes and pops its own deque end while thieves take from the other end, so the common case never contends
•
when threads are cheap, cap the scarce resource with a semaphore instead of pooling threads
P. Futures, promises and async composition
•
a placeholder for a value that will exist later, with the waiting built in
•
the producer sets a value or an exception into the shared state exactly once
•
std::future::get waits, returns the value once, and leaves the future unusable
•
an exception thrown inside the task resurfaces at the point where the result is retrieved
•
std::async with deferred runs the function on the calling thread at get time, or never runs at all
•
the future returned by std::async joins in its destructor, so discarding it silently serializes the program
•
thenApply/thenCompose attach a continuation rather than parking a thread on a result
•
waiting for every result versus taking the first to arrive, and what happens to the losers
Q. Coroutines, async/await and event loops
•
a coroutine returns control to its caller at a suspension point instead of holding the thread while it waits
•
a stackless coroutine can suspend only in its own frame; a fiber has a stack and can suspend anywhere in the call tree
•
shared state can change across every await, so invariants must hold at each one
•
an async function can only be awaited by another async function, so the property spreads up the call tree
•
one loop thread runs ready callbacks one at a time, which removes data races but not race conditions
•
a single synchronous call inside the loop stalls every other task on it
•
awaiting calls one after another is exactly as slow as blocking on each in turn
•
a task nobody awaits can fail with its exception discarded and its work half-done
•
a coroutine that resumes after an object it captured by reference has died is a dangling reference
•
epoll and kqueue report that a descriptor is ready; IOCP and io_uring report that the I/O is already done
R. Channels, CSP and actors
•
both sides block until the other arrives, so an unbuffered send is also a synchronization point
•
capacity lets a producer run ahead of the consumer until the buffer fills
•
a full channel blocks the sender, which is flow control with no extra mechanism
•
sending on a closed channel panics, and so does closing one twice, so exactly one goroutine must own the close
•
the second result tells a genuine zero value apart from a drained closed channel
•
send and receive on a nil channel block permanently, which is the idiom for disabling one select case
•
when several cases are ready one is chosen uniformly at random, which prevents a starved case
•
a goroutine blocked forever on a channel is never collected, so every send needs a guaranteed receiver or a cancellation case
•
an actor owns private state and processes one message at a time, so it needs no lock
•
an actor addresses a named recipient that owns a mailbox; CSP names the channel and not the peer
S. Cancellation and structured concurrency
•
a running task cannot be safely killed, so cancellation is a request the task has to observe
•
a derived context cancels when its parent does, so one signal reaches the whole subtree of work
•
a deadline is an absolute instant that composes down the call tree; a timeout restarts the clock at every hop
•
skipping the cancel function leaks its timer and any goroutine still watching the context
•
a blocking operation must also wait on the done channel, or the cancellation never takes effect
•
releasing resources with the already-cancelled context fails instantly and leaks the resource
•
catching InterruptedException and continuing must re-set the interrupt flag, or the request is silently lost
•
std::jthread requests a stop and joins in its destructor, so no thread outlives its scope
•
a child task's lifetime is bounded by the block that started it, and the block cannot exit while children run
•
one child's failure cancels its siblings and surfaces at the scope instead of vanishing into a log
T. Immutability, confinement and safe publication
•
state that never changes after construction cannot race, so it needs no lock at any scale
•
an object never mutated after being safely published is as safe as a truly immutable one
•
a final field holding a mutable list makes the reference constant and the contents free-for-all
•
copying at the boundary stops a caller's reference from silently becoming shared mutable state
•
a static initializer, a final field, a volatile or atomic reference, or insertion into a concurrent collection
•
a plain reference store can become visible before the object's own field writes, so another thread sees a half-built object
•
state that only one thread can reach needs no synchronization; the design constraint replaces the lock
•
per-thread state must be removed at task end, or it leaks into the next task that runs on that pooled thread
•
hand the object over and stop touching it, so exactly one thread owns it at every instant
U. Classic bugs and how they are actually found
•
without a volatile or atomic reference the second thread can see a non-null pointer to a not-yet-constructed object
•
checking a path's permissions and then opening it by name is exploitable; act on the handle you already hold, not on the name
•
a closure launched per iteration captures the variable, not its value, unless the language scopes it per iteration
•
when main returns, Go kills every remaining goroutine and the JVM abandons its daemon threads, mid-write
•
a sleep that "fixes" a race only makes it rarer, and the program slower
•
a print, a debugger or an unoptimized build changes the timing enough to hide the defect
•
happens-before instrumentation reports races only on code paths that actually ran, at a large time and memory cost
•
a latch, an injected delay or a deterministic scheduler reproduces the ordering a normal test never hits
•
measuring blocked time rather than CPU time is what finds the lock that is costing throughput
V. Consistency and progress models
•
every operation appears to take effect instantaneously at one point between its call and its return
•
sequential consistency ignores real time and linearizability respects it, which is why only the latter is a local property
•
a system assembled only from linearizable objects is linearizable, with no global argument required
•
a multi-object transaction property: some serial order of whole transactions explains the observed result
•
replicas converge once updates stop, with no promise about anything read in the meantime
•
"nothing bad ever happens" and "something good eventually happens" are proved by different arguments
•
atomic read/write solves consensus for one thread and CAS solves it for any number, which is why CAS is called universal
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.