Locks, Atomics & Coordination Flashcards

KOTLIN › JVM Concurrency

What is the escalation ladder for protecting shared state?
Immutability, then confinement to one thread or dispatcher, then an atomic for a single value, then a lock for several fields or a suspending section, then a specialised lock. Stop at the first rung that works: locks have the most failure modes.
What does a synchronized instance method lock on?
this, so two instances of the same class do not exclude each other. A static synchronized method locks the Class object instead, which is the shared one.
Why should you never lock on a String literal or boxed Integer?
Because they may be interned and therefore shared with completely unrelated code, so two unconnected classes can end up contending on the same monitor. Use a dedicated private val lock = Any().
What does ReentrantLock give you over synchronized?
tryLock with a timeout, lockInterruptibly, optional fairness, and multiple Condition wait sets. Both are reentrant and both create the same happens-before edge, so those are not the differences.
What is the cost of a fair ReentrantLock(true)?
Significant throughput. Fair means strict FIFO, so a running thread cannot barge ahead of queued waiters, which prevents starvation but forces a context switch on nearly every handoff.
How do you cap concurrent operations at five in coroutine code?
A coroutine Semaphore(5) with withPermit { }. A Mutex is a semaphore with one permit. Unlike limitedParallelism(5), it bounds how many are inside the section rather than how many are executing, so suspended requests still count.
What is the difference between CountDownLatch and CyclicBarrier?
A latch counts events, is one-shot, and lets waiters and counters be different threads. A barrier counts arriving threads, requires every participant to both arrive and wait, and resets for the next round.
What is compare-and-set?
A single CPU instruction that atomically checks whether a location still holds an expected value and writes a new one if so. Because check and write are one instruction, nothing can slip between them, which is the gap that makes count++ unsafe.
Why can atomics be slower than a lock under heavy contention?
Because CAS is optimistic and retries on collision, so many contending threads burn CPU spinning and discarding work. LongAdder fixes it by giving threads separate cells and summing on read.
What is the ABA problem?
CAS verifies the current value, not that the value never changed. A value going A to B and back to A passes the check even though the state moved on. AtomicStampedReference adds a version so an identical value with a different stamp fails.
What are the four conditions required for deadlock?
Mutual exclusion, hold and wait, no preemption, and circular wait. Breaking any one prevents it: lock ordering removes circular wait, tryLock with a timeout removes hold and wait.
How do you distinguish deadlock, livelock and starvation?
Deadlock: threads blocked forever, visible as monitor waits in a thread dump. Livelock: threads running and reacting to each other, making no progress and burning CPU. Starvation: progress happens, but one thread never gets its turn.
Why must you never hold a synchronized monitor across a suspending call?
Because a monitor is owned by the acquiring thread, and a coroutine can resume on a different one, so the release would come from a thread that never acquired it. Use a coroutine Mutex, which suspends and is not thread-owned.
When does ReentrantReadWriteLock beat plain synchronized, and what are its rules?
Read-heavy, write-rare data: many threads may hold the read lock concurrently, one thread the write lock exclusively. It is reentrant; downgrading (write -> read) is allowed, upgrading (read -> write) deadlocks. Under write-heavy load it can be slower than a plain lock.
Why use a coroutine Mutex instead of synchronized in suspend code?
synchronized is thread-based and blocks; a coroutine may suspend inside the critical section and resume on a different thread, so a monitor can't protect across a suspension point. mutex.withLock { } suspends instead of blocking and is released correctly regardless of resume thread.
How does Mutex.withLock differ from a JVM synchronized block, and why does that matter for coroutines?
synchronized blocks the thread, preventing any coroutine on that thread from running. Mutex.withLock suspends the coroutine instead, freeing the thread for other coroutines. This keeps the dispatcher responsive and avoids thread starvation in coroutine-heavy code.

Back to Locks, Atomics & Coordination