False sharing and cache lines
Two threads, two separate counters, and still they fight - over one 64-byte cache line.
Open this lesson in the learning hubKey points
- CPUs move memory in cache lines of about 64 bytes. Two variables inside one line are a single unit to the hardware.
- When core 1 writes its own counter, the line is invalidated on core 2, which must re-fetch it before touching its own counter.
- Nothing is logically shared, so no lock can help. The cost is invisible in the code and shows up only as poor scaling.
- Pad hot per-thread fields into separate lines. That is exactly what
LongAdderdoes with its striped cells. - Adjacent array slots are the usual trap:
counter[threadId]looks ideal and is the worst possible layout.
Example
import java.util.concurrent.atomic.AtomicLongArray;
public class Main {
static final int THREADS = 4;
static final int ITERATIONS = 2_000_000;
public static void main(String[] args) throws InterruptedException {
time(1); time(16); // warm the JIT up first
long crowded = time(1); // slots 0,1,2,3: one 64-byte cache line
long padded = time(16); // slots 0,16,32,48: one line each
System.out.println("neighbouring slots : " + crowded + " ms");
System.out.println("padded slots : " + padded + " ms");
System.out.println("same work, same threads, no lock - only the layout changed");
System.out.println("LongAdder pads for you, which is why it wins under contention");
}
static long time(int stride) throws InterruptedException {
AtomicLongArray cells = new AtomicLongArray(THREADS * stride);
Thread[] ts = new Thread[THREADS];
long start = System.nanoTime();
for (int t = 0; t < THREADS; t++) {
int slot = t * stride; // every thread owns its own slot
ts[t] = new Thread(() -> { for (int i = 0; i < ITERATIONS; i++) cells.incrementAndGet(slot); });
ts[t].start();
}
for (Thread t : ts) t.join();
return (System.nanoTime() - start) / 1_000_000;
}
}
Give every hot counter its own cache line, or use LongAdder and stop thinking about it.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.