伪共享(False Sharing)和缓存行(Cache Line)

缓存行(Cache Line)

直接说重点,概念什么的请自行百度,用最通俗的话来讲就是多核计算机的一个处理器会有多个核,每个核中会存在L1、L2缓存,多个核之间共享L3缓存,画个简单的图来表示一下:
伪共享(False Sharing)和缓存行(Cache Line)_第1张图片
变量位置与访问效率对比:

位置 执行效率
寄存器中 1个周期
CPU CACHE中 1~30个周期
主存中 50~200个周期
磁盘中 几千万个周期

以常见的windows操作系统来说,一个缓存行一般是64个字节的大小,cpu每次访问数据时,会逐一从L1,L2,L3中寻找,如果没有则会去主存中读取,读取不到会去磁盘中寻找,使用缓存需要发挥局部性原理,时间局部性空间局部性
时间局部性:例如有一个变量会被多次访问,那么把它加载到缓存中,后面多次访问都直接从缓存中命中。
空间局部性:可以充分利用64字节的大小,一次性把后续程序要用到的数据都加载进来,例如一个数组,可以把其全部加载进来,后面使用时会直接从缓存命中。

伪共享(False Sharing)

对象的成员变量在多线程环境下使用时,会出现一个伪共享False Sharing的概念。
先来复习一下基础知识
1.Java对象头都占8个字节
2.成员变量占用字节大小如下表

顺序 类型 字节数量
1 double 8字节
2 long 8字节
3 int 4字节
4 float 4字节
5 short 2字节
6 char 2字节
7 boolean 1字节
8 byte 1字节
9 对象引用 4或者8字节
10 子类字段 重新排序

那么在Java代码中如何产生伪共享呢?直接在代码中解释

public class FalseSharing {
    private int x;
    private int y;
    private int z;
}

上述代码中x,y,z由于缓存行的局部性原理,会一次性加载到统一缓存行中
伪共享(False Sharing)和缓存行(Cache Line)_第2张图片
根据MESI协议,当线程A尝试修改x变量时,同时线程B尝试修改z变量,看似两个互不相干的线程操作,实际上已经产生了伪共享,线程A在core1上优先修改后,这时这个缓存行状态已经由共享状态变更为修改状态,并且发起写操作至主存,并且会通知其他cpu核此时这个缓存行已经无效,需要去主存中重新读取缓存行,之后再进行线程B的写操作,看似两个毫无相关的变量已经在多线程环境下产生了先后顺序。

伪共享的解决方案

JDK6的时代,可以通过增加一些无用的long类型变量,并且加上对象头的8个字节,刚好把内存扩展到64字节或者64的整数倍。

 public final static class VolatileLong { 
        public volatile long value = 0L; 
        // 填充了6个long类型,6*8=48 ,48+8字节对象头+value的8字节,使得value和这几个在同一缓存行。
        public long p1, p2, p3, p4, p5, p6; 
}

JDK7中虚拟机对代码会有进一步的优化,优化掉不使用的变量等。只能把padding的内容放在基类里面。然后实现类去继承这个基类,但是效果显而易见非常的死板。

public class VolatileLongPadding {
    public volatile long p1, p2, p3, p4, p5, p6;   
}

public class VolatileLong extends VolatileLongPadding {
    public volatile long value = 0L;  
}  

JDK8中,Java已经支持了缓存行的填充,使用注解@Contended,该注解支持类以及变量,但是必须加上虚拟机参数-XX:-RestrictContended,才能生效,否则就是自欺欺人。
测试代码,从别的帖子copy了一个,大家可以参考一下。

public final class FalseSharing implements Runnable {  
    public static int NUM_THREADS = 4; // change  
    public final static long ITERATIONS = 500L * 1000L * 1000L;  
    private final int arrayIndex;  
    private static VolatileLong[] longs;  

    public FalseSharing(final int arrayIndex) {  
        this.arrayIndex = arrayIndex;  
    }  

    public static void main(final String[] args) throws Exception {  
        Thread.sleep(10000);  
        System.out.println("starting....");  
        if (args.length == 1) {  
            NUM_THREADS = Integer.parseInt(args[0]);  
        }  

        longs = new VolatileLong[NUM_THREADS];  
        for (int i = 0; i < longs.length; i++) {  
            longs[i] = new VolatileLong();  
        }  
        final long start = System.nanoTime();  
        runTest();  
        System.out.println("duration = " + (System.nanoTime() - start));  
    }  

    private static void runTest() throws InterruptedException {  
        Thread[] threads = new Thread[NUM_THREADS];  
        for (int i = 0; i < threads.length; i++) {  
            threads[i] = new Thread(new FalseSharing(i));  
        }  
        for (Thread t : threads) {  
            t.start();  
        }  
        for (Thread t : threads) {  
            t.join();  
        }  
    }  

    public void run() {  
        long i = ITERATIONS + 1;  
        while (0 != --i) {  
            longs[arrayIndex].value = i;  
        }  
    }  
}

@Contended
public class VolatileLong {
    public volatile long value = 0L;  
}

你可能感兴趣的:(java)