伪共享(False Sharing)是指在多线程环境中,多个线程操作不同但共享同一缓存行的数据,导致不必要的缓存一致性流量,从而降低系统性能。理解和解决伪共享问题对于优化多线程程序的性能非常重要。
缓存行和伪共享的基础知识
现代处理器使用缓存来提高内存访问速度,缓存以固定大小的块(称为缓存行,通常为64字节)存储数据。处理器在访问内存时,会将整行数据加载到缓存中。
伪共享的具体表现
当多个线程操作位于同一缓存行中的不同变量时,即使这些变量彼此独立,处理器也必须在每次写操作后进行缓存一致性维护。这会导致缓存行在不同处理器核之间频繁传输,严重影响性能。
举例说明
考虑以下示例,其中两个线程分别操作两个位于同一缓存行中的变量:
public class FalseSharingExample {
private static class SharedData {
public volatile long a;
public volatile long b;
}
public static void main(String[] args) throws InterruptedException {
SharedData data = new SharedData();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.a++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.b++;
}
});
long start = System.nanoTime();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
long end = System.nanoTime();
System.out.println("Duration: " + (end - start) / 1_000_000 + " ms");
}
}
在这个例子中,线程1和线程2分别操作变量a
和b
,由于这两个变量可能在同一个缓存行中,伪共享问题会导致性能下降。
解决伪共享问题
1. 通过填充变量来避免伪共享
在变量之间插入填充物,使它们占用不同的缓存行:
public class FalseSharingSolution {
private static class PaddedData {
public volatile long a;
private long p1, p2, p3, p4, p5, p6, p7; // 填充物
public volatile long b;
}
public static void main(String[] args) throws InterruptedException {
PaddedData data = new PaddedData();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.a++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.b++;
}
});
long start = System.nanoTime();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
long end = System.nanoTime();
System.out.println("Duration: " + (end - start) / 1_000_000 + " ms");
}
}
2. 使用 @Contended
注解
@Contended
注解是 JDK 提供的一种工具,用于避免伪共享。它通过在变量之间插入填充物来确保每个变量占用不同的缓存行。需要启用 JVM 参数 -XX:-RestrictContended
:
import sun.misc.Contended;
public class ContendedExample {
@Contended
public static class SharedData {
public volatile long a;
public volatile long b;
}
public static void main(String[] args) throws InterruptedException {
SharedData data = new SharedData();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.a++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
data.b++;
}
});
long start = System.nanoTime();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
long end = System.nanoTime();
System.out.println("Duration: " + (end - start) / 1_000_000 + " ms");
}
}
运行时需要加上 JVM 参数:
java -XX:-RestrictContended ContendedExample
总结
伪共享问题会导致多线程程序的性能显著下降,解决方法包括通过填充变量或使用 @Contended
注解来避免不同线程操作的变量共享同一缓存行。理解和解决伪共享问题对于优化多线程程序性能至关重要。