java

缓存行-伪共享

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 参数

关于作者

程序员,软件工程师,java, golang, rust, c, python,vue, Springboot, mybatis, mysql,elasticsearch, docker, maven, gcc, linux, ubuntu, centos, axum,llm, paddlepaddle, onlyoffice,minio,银河麒麟,中科方德,rpm