Concurrency
Threads, synchronized monitors, deadlocks, the Java Memory Model, and memory leaks.
6 questions
JuniorTheoryVery commonWhat does @Volatile guarantee, and what does it NOT guarantee?
What does @Volatile guarantee, and what does it NOT guarantee?
@Volatile guarantees visibility — a write is immediately seen by other threads, and reads/writes are not reordered around it (a happens-before edge). It does NOT make compound actions like count++ atomic: two threads can still race and lose an update.
Common mistakes
- ✗Believing
@Volatilemakescount++atomic across threads - ✗Thinking
@Volatileprovides mutual exclusion like a lock - ✗Assuming visibility implies atomicity of compound operations
Follow-up questions
- →Which class would you use to make
count++atomic instead of@Volatile? - →What is a happens-before relationship in the Java Memory Model?
JuniorDebuggingCommonWhy might neither thread finish when two locks are taken in opposite order?
Why might neither thread finish when two locks are taken in opposite order?
Thread 1 holds cup and waits for coffeeMaker; thread 2 holds coffeeMaker and waits for cup. Each waits on a lock the other holds — a deadlock, so neither proceeds. Fix it by acquiring both locks in one global order in every thread.
Common mistakes
- ✗Assuming
synchronizedre-entrancy prevents deadlock across two objects - ✗Thinking the OS scheduler eventually breaks the deadlock on its own
- ✗Believing a different lock order in each thread is harmless
Follow-up questions
- →What four conditions must all hold for a deadlock to occur?
- →How could a
tryLockwith timeout avoid this hang instead of fixed ordering?
JuniorTheoryCommonWhat is a memory leak on the JVM, and how does a WeakReference help?
What is a memory leak on the JVM, and how does a WeakReference help?
A leak is an object the program no longer needs but that stays reachable from a GC root, so the collector never frees it. A WeakReference points at an object without keeping it alive — the GC may reclaim a weakly-referenced object, unlike a strong reference.
Common mistakes
- ✗Equating a leak with an immediate
OutOfMemoryErrorrather than slow reachable growth - ✗Thinking the JVM uses reference counting instead of reachability tracing
- ✗Believing a
WeakReferenceis collected eagerly on every read
Follow-up questions
- →How does a
WeakReferencediffer from aSoftReference? - →How does a tracing garbage collector decide an object is garbage?
MiddleDebuggingOccasionalWhy does this producer/consumer with a synchronized busy-loop stall?
Why does this producer/consumer with a synchronized busy-loop stall?
Whichever thread enters synchronized(phone) first holds the monitor for its whole infinite loop and never releases it, so the other blocks forever — a deadlock. Fix it with wait/notify: a thread calls phone.wait() to release the monitor while idle, the other calls notify() after changing state.
Common mistakes
- ✗Thinking
synchronizedreleases the monitor between loop iterations - ✗Blaming a data race instead of the never-released monitor
- ✗Believing
@Volatilealone lets the two busy-loops cooperate
Follow-up questions
- →Why must
waitandnotifybe called while holding the object's monitor? - →How does
waitrelease the monitor while a busywhileloop does not?
MiddleDebuggingOccasionalWhy does this Activity still leak after rotation despite a WeakReference?
Why does this Activity still leak after rotation despite a WeakReference?
An inner class holds an implicit strong reference to its outer MainActivity (this$0), and the running Thread keeps the Job alive, so the old Activity stays reachable — the WeakReference is a red herring. Fix: make Job a nested, non-inner class.
Common mistakes
- ✗Blaming the
WeakReferenceinstead of the implicitinner-class outer reference - ✗Thinking
innercarries no reference to its enclosing class - ✗Switching to
SoftReferencerather than cutting the strong outer link
Follow-up questions
- →What is the difference between an
inner classand anestedclass in Kotlin? - →Why does a long-running
Threadact as a GC root for everything it holds?
MiddleDebuggingRareHow can two threads with interleaved writes/reads print [0, 0]?
How can two threads with interleaved writes/reads print [0, 0]?
Each thread's write may not be visible to the other, and reordering lets both reads run before either write lands — so [0, 0] is legal. @Volatile on x and y adds happens-before edges that exclude [0, 0], but [1, 1] still needs synchronization or a join.
Common mistakes
- ✗Assuming program-order writes are always visible to other threads instantly
- ✗Thinking
@Volatileguarantees the[1, 1]result, not just excluding[0, 0] - ✗Believing single writes need atomicity rather than visibility/ordering here
Follow-up questions
- →Why does
@Volatileexclude[0, 0]but not force[1, 1]? - →How would a
joinbefore the reads guarantee[1, 1]?