Interview Prep Zoneby CuriouserLabs

Question 4 of 5 · staff level

Cache coherence and false sharing: what does MESI do, and why can two threads slow each other down without sharing any data?

🎤 Say this first

Each core caches privately, so hardware must keep copies consistent: MESI gives every line a state (Modified/Exclusive/Shared/Invalid) — to write, a core must own the line exclusively, which invalidates all other copies; their next read misses and fetches the update. Coherence guarantees eventual single-value truth per line — it does NOT give you ordering across lines (that's the memory model + barriers). False sharing is coherence's friendly fire: two threads updating different variables that happen to share one 64-byte line ping-pong ownership at ~50–100ns a hop — a 10× slowdown with zero logical sharing. Fix: pad/space hot per-thread data (@Contended, striping — it's why LongAdder works).

The full picture

False sharing, made visible
class Counters {                     // both fields in ONE cache line:
    volatile long threadA;            // every A-write invalidates B's line copy
    volatile long threadB;            // every B-write invalidates A's
}   // two cores, zero shared data, ~10x slower than expected

@jdk.internal.vm.annotation.Contended  // or manual padding / separate objects
class Padded { volatile long value; }  // JVM pads to its own line (-XX:-RestrictContended)

// The library answer you already use:
LongAdder adder = new LongAdder();     // striped cells, one per contending thread,
adder.increment();                     // each cell padded — no line ping-pong
  • MESI in four sentences: read miss → line arrives Shared (or Exclusive if alone); write → broadcast invalidate, become Modified; another core reads your Modified line → you supply it, both go Shared; eviction of Modified → write back. Everything else is protocol engineering (MOESI, directories instead of bus snooping at high core counts).
  • Coherence ≠ consistency (the distinction interviewers probe): MESI makes all cores agree on each line's value eventually and per-line; store buffers and reordering still let cores disagree on the order of writes to different lines — that gap is exactly what memory models, barriers and volatile manage.
  • Diagnosing false sharing: contended-looking scaling collapse with no visible lock; confirm with perf c2c / HITM events, or the classic experiment — add padding, watch throughput jump. Suspects: adjacent counters/flags in one object, adjacent array slots written per-thread (histogram bins!), ring-buffer head/tail (Disruptor pads both).
  • Where the JDK already saved you: LongAdder/Striped64, ConcurrentHashMap counter cells, Thread-local designs — recognizing striping as 'the false-sharing fix' turns three APIs into one idea.

🔄 Likely follow-up questions

  • Why does volatile writing intensify false-sharing costs specifically?
  • How does the Disruptor's padding strategy work and what does it protect?
  • What is a store buffer and how does it relate to the x86 vs ARM model difference?