Memory Models & Visibility
The advanced core: threads do not necessarily observe memory operations in source order. Happens-before, compiler and CPU reordering, barriers, false sharing, cache coherence traffic, safe publication and why double-checked locking is the canonical cautionary tale.
Threads do not necessarily observe memory operations in the order your source code wrote them. A memory model is the contract that says which orders are possible — and the language's contract is a different object from the CPU's.
Q · What does a memory model actually define, and why is the language's model not the CPU's?
Happens-before is the relation that answers "will the other thread see it?". It is not about wall-clock time. It is a partial order built from specific paired constructs, and two operations with no path between them are unordered no matter which one ran first.
Q · What actually makes a write in one thread visible to a read in another?
Two independent reorderers sit between your source and what another thread observes. Both are allowed to move memory operations as long as *your own thread's* observable behaviour is unchanged. Synchronization is the only thing that constrains either of them.
Q · Who reordered my code — the compiler, the CPU, or neither — and what actually stops it?
A barrier says which of your memory operations may not move across this point. It does not "flush the cache" — caches are already coherent. Getting that distinction right is what separates a correct ordering argument from a plausible-sounding one.
Q · What does a memory barrier actually constrain, and why is "flush the cache" the wrong mental model?
Two threads update two different counters and never touch each other's data. The counters sit in the same cache line, so every update takes the line away from the other core. Correctness is untouched; the speedup you added a thread for never appears.
Q · Two threads never touch the same variable — why did adding the second thread make it slower?
Each core has its own cache, and the hardware keeps them consistent for you. That consistency is not free: a location written by many cores generates traffic on every write, which is why one shared counter can cap the throughput of a sixteen-core machine.
Q · What does a shared write actually cost when every core has its own cache?
Making a reference visible is not the same as making the object visible. A reader can hold a perfectly valid pointer to an object whose fields it cannot yet see — a half-constructed object, from the reader's point of view, with no null and no error to warn it.
Q · How does one thread safely hand a newly constructed object to another without the other seeing it half-built?
Check without the lock, lock, check again, initialise. It looks like a pure optimization and it is the standard example of why memory-model reasoning matters — the fast path can hand out a reference to an object it cannot see. The fix exists in every language and is different in each.
Q · Why is the obvious "check, lock, check again" lazy initialization broken, and why is the fix language-specific?