Chapter 13 Flashcards — Concurrency
flashcards clean-code concurrency threading
What is the core idea behind using concurrency as a decoupling strategy?
?
Concurrency decouples what gets done from when it gets done. A single-threaded system ties the execution order of tasks to their logical meaning. Concurrency lets the system overlap I/O wait with useful work, keeping what is computed independent of scheduling order.
What are the two primary motivations for adding concurrency to a system?
?
Throughput: overlap I/O wait across many tasks (e.g., a web server handling 1,000 simultaneous requests). Responsiveness: keep a UI or service responsive while doing background work (e.g., downloading a file without freezing the interface).
Why does concurrency NOT always improve performance?
?
Concurrency only helps when there is significant wait time to overlap. If the work is purely CPU-bound and threads exceed the core count, context-switching overhead hurts performance. On a 4-core machine, 100 threads doing pure computation is slower than 4 threads, not faster.
What is the myth “design does not change with concurrency” and why is it false?
?
Programmers sometimes assume they can add threads to an existing design without rethinking it. This is false. Concurrency forces you to reason about shared state, invariants that span multiple operations, and which compound actions must be atomic. A class that is correct single-threaded can be fatally broken multi-threaded without any code change.
Give an example of a race condition on a bank account check-then-act pattern.
?
Thread 1 and Thread 2 both read balance = 1000, both pass the check if (balance >= 500), and both call deduct(500). Balance ends at 0 instead of 500 — the invariant “balance cannot go negative” is violated. The individual getBalance() and deduct() methods may each be synchronized, but the compound check-then-act is not atomic.
What is the Single Responsibility Principle as applied to concurrency?
?
Concurrency-related code should live in its own class, separate from business logic. Concurrency has its own lifecycle, its own failure modes (races, deadlocks), and its own testing requirements. Mixing synchronization code with business rules makes both harder to understand and test.
What does “limit the scope of shared data” mean in practice?
?
Encapsulate shared mutable state behind the narrowest possible interface. Minimize the number of places in code that can read or write shared data. Keep synchronized blocks covering only the lines that genuinely need protection — never hold a lock while doing slow I/O or computations that do not access shared state.
Why is making entire methods synchronized a problem even when it is technically correct?
?
Synchronizing an entire method holds the lock for its full duration. If the method does slow I/O (HTTP calls, database queries), all other threads wait for the lock even though the I/O doesn’t touch shared state. This creates unnecessary contention and degrades throughput. Only the lines that actually read/write shared data need the lock.
What is the “use copies of data” defense principle and when does it apply?
?
Instead of sharing a mutable object across threads (requiring synchronization), give each thread its own snapshot copy. Take one synchronized copy at the start, then work on that copy with no locks needed. Applicable when copy cost (time + memory) is acceptable, which it often is compared to the complexity of fine-grained synchronization.
What is the “threads should be as independent as possible” principle?
?
Partition data into independent subsets and assign each subset to one thread. The ideal thread reads from its own input and writes to its own output, never touching shared state. When threads share no data, there is nothing to synchronize and no race conditions are possible.
What Java class provides a thread-safe, blocking producer-consumer queue?
?
java.util.concurrent.BlockingQueue (e.g., LinkedBlockingQueue, ArrayBlockingQueue). put() blocks when the queue is at capacity; take() blocks when the queue is empty. No manual synchronization is needed — the blocking behavior replaces condition variables.
What is the C++ idiom for safely locking a mutex and ensuring it is released?
?
std::lock_guard<std::mutex> (or std::unique_lock for condition variables). lock_guard acquires the mutex on construction and releases it when it goes out of scope — even if an exception is thrown. This is the RAII idiom for locking: Resource Acquisition Is Initialization.
What Python class provides a thread-safe, blocking FIFO queue?
?
queue.Queue from the standard library. put() blocks when the queue is full (if maxsize > 0); get() blocks when the queue is empty. All internal locking is handled by the class itself. Equivalent to Java’s BlockingQueue for Python threading.
What is the Producer-Consumer execution model and what is its key synchronization concern?
?
One or more producer threads generate items and place them in a bounded buffer. One or more consumer threads remove and process items. Producers must block when the buffer is full; consumers must block when it is empty. The buffer is the shared resource requiring synchronization. The risk is busy-waiting — always use blocking operations (BlockingQueue.take(), condition_variable.wait()) rather than polling.
What is the Readers-Writers execution model and what is the main failure mode?
?
A shared resource allows multiple concurrent readers but requires exclusive access for writers. Readers can proceed simultaneously; a writer blocks all readers and other writers. The main failure mode is writer starvation: if readers continuously hold the lock, writers can never acquire it. Solutions prioritize writers or use fair scheduling.
What Java class implements the Readers-Writers pattern?
?
java.util.concurrent.locks.ReentrantReadWriteLock. Call rwLock.readLock().lock() for read operations (multiple threads can hold the read lock simultaneously) and rwLock.writeLock().lock() for write operations (exclusive). Always release in a finally block.
What C++ type (C++17) provides shared (read) and exclusive (write) locking?
?
std::shared_mutex. Use std::shared_lock<std::shared_mutex> for concurrent readers and std::unique_lock<std::shared_mutex> for exclusive writers. Available since C++17; before that, use Boost.SharedMutex or a hand-rolled solution.
What is the Dining Philosophers problem and what concurrent hazard does it illustrate?
?
Five philosophers sit at a table with five forks. Each needs two forks to eat. If every philosopher picks up their left fork simultaneously, all are waiting for a right fork that no one releases: deadlock. It illustrates that symmetric resource acquisition creates circular wait — one of the four Coffman conditions for deadlock.
What are the four Coffman conditions required for deadlock to occur?
?
- Mutual exclusion — resources cannot be shared. 2. Hold and wait — a thread holds one resource while waiting for another. 3. No preemption — resources cannot be taken forcibly. 4. Circular wait — a circular chain of threads, each waiting for a resource held by the next. Breaking any one condition prevents deadlock. The most practical: break circular wait by imposing a global ordering on lock acquisition.
How does breaking symmetry solve the Dining Philosophers deadlock?
?
One philosopher acquires forks in reverse order (right fork first, then left) while all others pick left first. This breaks the circular wait condition: the “reversed” philosopher will block on the right fork rather than holding the left fork and waiting for the right — so the chain cannot form a complete circle. No deadlock is possible.
What is livelock and how does it differ from deadlock?
?
In a deadlock, threads are blocked — they make no progress and consume no CPU. In a livelock, threads are active but keep undoing and redoing the same actions without making real progress (e.g., two threads each back off, retry, and collide repeatedly). Both result in no useful work. Livelocks are often caused by overly polite retry logic.
Why is it dangerous to have multiple synchronized methods on a shared class when callers use them together?
?
Each method is individually atomic, but a sequence of two calls from client code is not atomic as a unit. Between the two calls, another thread can modify state, invalidating the assumption made in the first call. The fix: provide a single synchronized method that performs the complete compound operation, so the class’s invariants are maintained within one lock acquisition.
What is client-side locking and why is it fragile?
?
Client-side locking means the caller acquires the object’s lock before calling multiple methods. It is fragile because: (a) it requires all callers to follow the same convention — easy to forget; (b) it exposes the lock as part of the API; (c) if the underlying class changes its locking strategy, all clients break. Prefer server-side locking: move compound operations inside the shared class.
Why is writing correct shutdown code considered hard in concurrent systems?
?
Threads may be blocked in BlockingQueue.take() or waiting on a condition variable with no items to unblock them. Simply calling thread.interrupt() only works if the thread is in an interruptible call. If it is not, the interrupt is silently ignored. Shutdown must be designed in from the start, using mechanisms like poison pills (sentinel values) or ExecutorService.shutdown() + awaitTermination().
What is the poison pill pattern for shutting down worker threads?
?
Place a sentinel value (a special Task object whose identity signals “stop”) into the BlockingQueue — one per worker thread. When a worker dequeues the sentinel, it exits its loop cleanly. This guarantees every worker processes all remaining tasks before seeing the sentinel, and exits without leaving work unfinished. Works with any BlockingQueue-based design.
What does “treat spurious test failures as candidate threading issues” mean?
?
A test that fails once and then passes on re-run is often testing a race condition. The failure happened because the thread scheduler happened to produce an interleaving that exposed a bug; the subsequent pass happened because the scheduler did not. Dismissing the failure as a fluke is dangerous — it is almost certainly a real concurrent bug that will reappear in production under load.
Why should you run concurrent tests with more threads than processors?
?
When thread count exceeds core count, the OS must context-switch more frequently, increasing the variety of possible interleavings. Race conditions that are hidden when threads run uninterrupted on dedicated cores are exposed when the scheduler frequently interrupts them mid-operation.
What is “jiggling” in the context of testing concurrent code?
?
Inserting deliberate calls to Thread.sleep(1), Thread.yield(), or Object.wait() at critical points in production code (during testing only) to artificially change the scheduling order and force interleavings that expose race conditions. Instrumented code is removed before deployment. This is also called stress testing with instrumentation.
What is the GIL in Python and how does it affect concurrent code?
?
The Global Interpreter Lock is a mutex in CPython that allows only one thread to execute Python bytecode at a time. This means Python threads do not run in true parallel for CPU-bound work. For I/O-bound work, threads still help because the GIL is released during I/O calls. For CPU-bound parallelism, use ProcessPoolExecutor (separate processes have separate GILs) or asyncio (cooperative single-threaded concurrency).
What is std::atomic in C++ and when should you use it instead of a mutex?
?
std::atomic<T> provides lock-free atomic operations on simple types (integers, pointers, booleans). Operations like fetch_add, compare_exchange_strong, and store are guaranteed to be indivisible without a mutex. Use std::atomic for simple counters and flags where the operation is a single read-modify-write. Use a std::mutex when the critical section spans multiple steps or data structures.
What is CompletableFuture in Java and how does it relate to concurrency?
?
CompletableFuture<T> (Java 8+) represents the result of an asynchronous computation. It can be chained with thenApply(), thenCompose(), thenCombine(), and exceptionally() to build non-blocking pipelines. It decouples the submission of work from the retrieval of results, and handles callback composition without explicit thread management. Prefer it over raw Thread or Runnable for async workflows.
Total Cards: 30
Review Time: ~25 minutes
Priority: HIGH
Last Updated: 2026-04-14