伪共享问题和@Contended

CPU读取内存数据时并非一次只读一个字节,而是会读一段64字节长度的连续的内存块(chunks of memory),这些块我们称之为缓存行(Cache line)。

假设你有两个线程(Thread1和Thread2)都会修改同一个volatile变量x:

    volatile long x;

如果Thread1先改变x的值,然后Thread2又去读它:

    Thread 1: x=3;
    Thread 2: System.out.print(x);

那么x所在缓存行上的所有64个字节的值都要被重新加载,因为CPU核心间交换数据是以缓存行为最小单位的。当然Thread1和Thread2是有可能在同一个核心上运行的,但我们此处假设两个线程在不同的核心上运行。
已知long类型占8个字节,缓存行长度为64个字节,那么一个缓存行可以保存8个long型变量,我们已经有了一个long型的x,假设x所在缓存行里还有其他7个long型变量,v1到v7:x, v1, v2, v3, v4, v5 ,v6 ,v7.

如何解决伪共享?

1.填充

public class FalseSharingWithPadding {
    public volatile long x;
    public volatile long p2; // padding
    public volatile long p3; // padding
    public volatile long p4; // padding
    public volatile long p5; // padding
    public volatile long p6; // padding
    public volatile long p7; // padding
    public volatile long p8; // padding
    public volatile long v1;
}

然而,JVM可能会清除无用字段或重排无用字段的位置,这样的话,可能无形中又会引入伪共享。我们也没有办法指定对象在堆内驻留的位置。

2.@Contended

    public class Point {
        int x;
        @Contended
        int y;
    }

上面的代码将x和y置于不同的缓存行。@Contented注解将y移动到远离对象头部的地方,(以避免和x一起被加载到同一个缓存行)。

posted @ 2023-03-27 15:01  刚刚好。  阅读(29)  评论(0编辑  收藏  举报