缓存行-伪共享
CPU 访问某个变 量 时,首先会去看 CPU Cache 内是否有该变量,如果有则直接从 中获取,否则就去主内存里面获取该变 量 ,然后把该变量所在内存区域的一个 Cache 行大 小的内存复制到 Cache 中。 由于存放到 Cache行的是内存块而不是单个变量,所以可能会把多个变量存放到一个 Cache行中。 当多个线程同时修改一个缓存行里面的多个变量时, 由于同时只能有 一 个线程操作 缓存行 ,所以相比将 每个变 量放到 一 个缓存行,性能会有所 下降,这就是伪共享。
伪共享的产生是因为 多个变量被放入了一个缓存行中,并且多个线程 同时去写入缓存 行中不同的变量 。
那么 为何 多个变量会被放入 一个缓存行呢?
其实是 因为缓存与内存交换 数据 的单位就是缓存行 , 当 CPU 要访问的变量没有在缓存中找到时,根据程序运行的局部性原理, 会把该变量所在内存中大小为缓存行的内存放入缓存行。
也就是地址连续的多个变量才有可能会被放到一个缓存行中。当创建数组时, 数组里面的多个元素就会被放入同一 个 缓存 行 。
实在正常情况下单线程访 问时将数组元素放入一个或者多个缓存 行对代码执行是有利的,因为数据都在缓存 中 ,代码执行会更快。
如何避免伪共享
在 JDK 8 之前 一般都是通过字节填充的方 式来避免该问题,也就是创建 一个变量时使 用填充字段填充该变量所在的缓存行,这样就避免了将多个变量存放在同 一个缓存行中。
JDK 8提供了一个 sun.misc.Contended注解,用来解决伪共享问题。
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
@Contended 注解只用于 Java 核心 类。
如果用户类路径下的类需要使用这个注解, 则 需要添加 NM 参数 :-XX:-RestrictContended。
填充的宽度默认为 128,要自定义宽度 则可以设 置 -XX:Con nd巳dPaddingWidth 参数