一、缓存行(Cache Line)
缓存行(Cache Line)是CPU缓存(Cache)中数据存储的最小单位,通常为 64字节。
当CPU从主存中读取数据时,会以缓存行为单位加载数据,而不是单独加载某个变量或某几个字节。
缓存行的特点:
空间局部性:CPU读取数据时,会顺带将周围的数据一起加载进缓存,以提高后续访问效率。
一致性协议:缓存行是缓存一致性协议(如MESI协议)进行数据同步的基本单位。
固定大小:主流CPU架构通常缓存行大小是64字节。
二、伪共享(False Sharing)
伪共享是指多个线程在各自处理不同变量时,这些变量正好位于同一个缓存行中,导致线程频繁争夺同一缓存行的控制权,使缓存行不断在各CPU核心间传递,严重影响程序性能。
伪共享的本质并非真正的共享数据,而是由于CPU缓存一致性协议引发的缓存行竞争。
三、伪共享产生原因与过程
举个例子:
假设缓存行大小为64字节,有两个线程分别访问两个变量:
public class Counter {
volatile long x = 0;
volatile long y = 0;
}
虽然线程A只访问
x
,线程B只访问y
,看似没有共享数据;但由于
x
和y
紧密相邻,很可能被放在同一个64字节的缓存行中;当线程A修改
x
时,缓存一致性协议会使线程B的缓存行失效,同理线程B修改y
时也会导致线程A缓存失效;最终造成大量缓存行的无效、更新、传递,引发性能损耗。
四、如何避免伪共享?
填充缓存行(Padding):
在变量之间添加额外的字节,使变量分布在不同缓存行中。
public class CounterPadded {
volatile long x = 0;
long p1, p2, p3, p4, p5, p6, p7; // padding
volatile long y = 0;
}
使用@Contended注解(JDK8及以上):
@jdk.internal.vm.annotation.Contended public class CounterContended { volatile long x = 0; volatile long y = 0; }
使用时需添加JVM参数:
-XX:-RestrictContended
合理设计数据结构:
尽量避免多线程频繁访问的数据被放入同一个结构体或类实例中,减少缓存行共享可能性。
五、伪共享实际影响与性能表现
伪共享导致的性能损耗可以非常严重,有时可能使程序性能降低数倍;
并发越高,线程数越多,伪共享的性能损耗越明显;
大量时间耗费在缓存一致性协议上的缓存行同步操作。
六、典型应用场景及解决方案示例
典型场景:
多线程计数器(如业务计数、访问次数统计);
并发队列、并发数组访问。
实际优化案例:
假设有一个高性能计数器:
public class PaddedAtomicLong {
private volatile long value = 0;
// Padding fields, each long is 8 bytes, 7 fields * 8 bytes = 56 bytes padding
public long p1, p2, p3, p4, p5, p6, p7;
public void increment() {
value++;
}
public long get() {
return value;
}
}
添加padding后,每个计数器占据独立缓存行,避免伪共享。
总结:
缓存行 是CPU缓存操作的最小单位(通常为64字节)。
伪共享 指变量未真正共享,但位于同一缓存行中,导致性能损耗。
通过缓存行填充(padding)和合理设计数据结构可有效避免伪共享,提高并发程序的性能。