The full picture
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
volatilemanage. - 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,ConcurrentHashMapcounter cells, Thread-local designs — recognizing striping as 'the false-sharing fix' turns three APIs into one idea.