False Sharing and @Contended

Naveen AnamaneniUpdated · 4 min read

False sharing is a hidden performance problem in multithreaded code. CPUs move memory between main memory and their caches in blocks called cache lines, usually 64 bytes. If two threads on different cores keep writing different variables that happen to sit on the same cache line, every write forces the other core to throw away its copy of the whole line and fetch it again. The threads don't share any data, yet they slow each other down as if they did. In this lesson, you will see false sharing in a benchmark and learn how padding and @Contended fix it.

How False Sharing Happens

Different variables on one cache line make cores fight over the line

Example: Measuring False Sharing

Let's see an example where two threads each increment their own counter 20 million times. The counters live in an AtomicLongArray, so every increment really writes to memory (a plain long[] loop would be optimized into a single write by the JIT compiler, hiding the effect). In the first version the two counters are next to each other in an array; in the second they are 16 longs (128 bytes) apart. Times depend on the computer, so the output is a sample.

FalseSharing.java▶ Try it
import java.util.concurrent.atomic.AtomicLongArray;
 
public class FalseSharing {
    static final AtomicLongArray counters = new AtomicLongArray(32);
 
    static long run(int indexA, int indexB) throws InterruptedException {
        counters.set(indexA, 0);
        counters.set(indexB, 0);
        Thread a = new Thread(() -> {
            for (int i = 0; i < 20_000_000; i++) {
                counters.incrementAndGet(indexA);
            }
        });
        Thread b = new Thread(() -> {
            for (int i = 0; i < 20_000_000; i++) {
                counters.incrementAndGet(indexB);
            }
        });
        long start = System.nanoTime();
        a.start();
        b.start();
        a.join();
        b.join();
        return (System.nanoTime() - start) / 1_000_000;
    }
 
    public static void main(String[] args) throws InterruptedException {
        run(0, 1);
        run(0, 16);
        long sameLine = run(0, 1);
        long padded = run(0, 16);
        System.out.println("Neighbors (same cache line): " + sameLine + " ms");
        System.out.println("Padded (separate lines):     " + padded + " ms");
    }
}
Output
Neighbors (same cache line): 187 ms
Padded (separate lines):     71 ms

Explanation: Both versions do exactly the same work, and the threads never touch each other's counter. When the counters are neighbors, they share a cache line, so each increment by one core invalidates the other core's cache, and the program ran about two and a half times slower. In one of three runs on the test machine the difference disappeared, probably because the operating system placed both threads on cores that share a cache; this is why such effects must be measured several times. With 128 bytes of space between them, each counter lives on its own line and the cores work independently. (Each version runs once first to warm up the JIT compiler.)

Fixes

FixHow
PaddingPut unused fields or array slots between hot variables
@jdk.internal.vm.annotation.ContendedTells the JVM to pad a field; JDK-internal, works for application code only with -XX:-RestrictContended and an --add-exports flag
Per-thread dataGive each thread its own counter and combine at the end
LongAdderAlready uses padded cells to avoid false sharing

Common Mistakes

Packing per-thread counters into one small array or object. That's the classic false-sharing layout.

Padding everything. It wastes memory where there's no contention.

Benchmarking without a warm-up. The first runs include JIT compilation time.

Practice

  1. Easy: Run FalseSharing several times and calculate the average slowdown on your computer.
  2. Harder: Try distances of 2, 4 and 8 array slots between the counters and find the smallest distance that removes the slowdown on your machine.

Quick check: False sharing

4 questions
1What is a cache line?
2What causes false sharing?
3How do you fix it?
4Which JDK class already avoids false sharing for counters?

Key Takeaways

Was this tutorial helpful?

Lesson 423 of 482