The Java Memory Model (JMM) Quiz

KOTLIN › Concurrency

Thread B loops on a plain Boolean and never exits, even though thread A set the flag seconds ago. What does the JMM say about this?

Answer: It's legal: without a happens-before edge, B may read its cached false forever

No synchronisation means no visibility guarantee, ever. The JIT may even hoist the read out of the loop entirely. A volatile flag (or any lock/atomic protecting it) creates the edge that forces B to observe the write.

What exactly does the Java Memory Model define?

Answer: When one thread's writes are guaranteed to be visible to another thread

The JMM is a visibility-and-ordering rulebook, not a scheduler or memory layout spec. It defines which executions (with caching and reordering) are legal, and which synchronisation actions force writes to be seen.

Two threads run count++ 1000 times each on a @Volatile int. The result is 1400. Why?

Answer: count++ is read-add-write; volatile fixes visibility, not the interleaving

Both threads can read the same value, both add one, and both store the same result: one increment vanishes. Each individual read and write was perfectly visible; the compound operation was never atomic. This is THE most common concurrency gotcha.

Which is the correct fix for that lost-increment race?

Answer: AtomicInteger.incrementAndGet(), or a lock around the whole increment

The read-modify-write must execute as one indivisible step: AtomicInteger does it with a CAS loop, a lock does it with mutual exclusion. Double-reading or yielding just reshuffles the interleavings.

A thread reads a non-null instance whose fields are still zero/default. What made this possible?

Answer: The reference store was reordered before the constructor's field stores

The compiler/CPU may reorder independent stores: publish the reference first, initialise fields after. Another thread wins the race and dereferences a half-built object. This is the ordering problem, distinct from visibility and atomicity.

Which set of tools forbids that half-built-object publication?

Answer: A volatile reference, synchronized access, or final fields

Volatile ordering semantics forbid moving the field stores past the reference store; a lock brackets construction and publication; final fields get a freeze at the end of construction that guarantees initialised values to all readers.

If action A happens-before action B, what is guaranteed?

Answer: A's writes are visible to B, and A is ordered before B

That is the entire contract: visibility plus ordering. It says nothing about which thread runs what or about atomicity. All the sync tools (volatile, monitors, atomics, thread start/join) are just different ways to create these edges.

Which pair forms a happens-before edge?

Answer: A write to a volatile field, then a later read of that same field

Volatile write then read of the SAME field is a canonical edge, and it publishes everything the writer did before the write (not just that field). Different monitors create no edge between each other, and sleep has no memory semantics.

Beyond mutual exclusion, what memory guarantee does synchronized give?

Answer: Unlock happens-before the next lock of the same monitor, so the next holder sees all prior writes

Release then acquire of the same monitor is a happens-before edge covering everything the previous holder wrote, any fields anywhere. That's why consistently locking on one monitor needs no volatile fields at all.

What does Thread.join() guarantee to the caller when it returns?

Answer: Every write the joined thread made is now visible

Everything in a thread happens-before another thread successfully returning from join() on it, so all its writes (plain or volatile) are visible afterwards. start() is the mirror edge into the thread.

Why are immutable objects (all fields final/val, no escape during construction) thread-safe with zero synchronisation?

Answer: Final fields are frozen at construction: every thread sees them fully initialised

The JMM gives final fields special semantics: after safe construction, any thread that obtains the reference sees the final fields correctly initialised, even via a data race. State that never changes cannot be seen inconsistently.

What does 'ConcurrentHashMap is thread-safe' precisely mean in JMM terms?

Answer: Each operation is atomic and creates happens-before edges, but your own check-then-act sequences still race

The map hand-rolls the JMM machinery internally, so put/get are individually correct and visible. Compound logic (if absent then put) is YOUR race unless you use the atomic compound ops: putIfAbsent, compute, merge.

In Kotlin, val config by lazy { load() } is read by many threads. Is initialisation safe?

Answer: Yes: lazy defaults to SYNCHRONIZED mode, so one thread initialises and all see the result

LazyThreadSafetyMode.SYNCHRONIZED is the default: initialisation runs once under a lock and publication rides the monitor's happens-before edge. PUBLICATION allows racing initialisers with one winner; NONE is single-thread only.

Two coroutines on Dispatchers.Default increment a plain var 1000 times each via launch. What is true?

Answer: It races exactly like threads: coroutines on a multi-threaded dispatcher share the JMM's rules

Coroutines are not a memory model: on a multi-threaded dispatcher they hit the same visibility/atomicity rules. Fixes are the same spirit: confine state to one thread/dispatcher, use atomics, or a Mutex. (Suspension points do create happens-before edges, which is why confined code is safe.)

Back to The Java Memory Model (JMM)