转载

Java也谈谈伪共享FlashSharing

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xzknet/article/details/82630919

1、什么是伪共享

  • 在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素。
  • 伪共享的非标准定义为:缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享( https://www.cnblogs.com/cyfonly/p/5800758.html )。
    总之就是很麻烦,因为一个cpu的缓存问题,可以在高并发系统中让你的程序卡的要死.
    Java也谈谈伪共享FlashSharing

1-1 现象重现

package com.csdn.blog.xzknet.test.flashsharing;

public final class FalseSharingFail implements Runnable {
    public final static int NUM_THREADS = 4; // change
    public final static long ITERATIONS = 500L * 1000L * 1000L;
    private final int arrayIndex;

    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    static {
        for (int i = 0; i < longs.length; i++) {
            longs[i] = new VolatileLong();
        }
    }

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

    public static void main(final String[] args) throws Exception {
        System.out.println("VolatileLong未解决了伪共享问题.所以每次longs[i]都会将其他的longs[i+1]或者longs[i-1]的64位加载进来.");
        final long start = System.nanoTime();
        runTest();
        System.out.println("持续时间 = " + (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 FalseSharingFail(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;
        }
    }

    public final static class VolatileLong {
        public volatile long value = 0L;
        //public long p1, p2, p3, p4, p5, p6; // comment out
    }
}

1-2 怎么解决伪共享

解决方法很简单,就是把连续的内存块儿给搞一些无用的引用之类的放上去,例如long占8个字节object引用占4个字节,总之,可能会出现伪共享的字段后你就使劲加吧加够64个字节就是了.例如上面的问题改造方法见下面的代码.

package com.csdn.blog.xzknet.test.flashsharing;

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

    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    static {
        for (int i = 0; i < longs.length; i++) {
            longs[i] = new VolatileLong();
        }
    }

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

    public static void main(final String[] args) throws Exception {
        System.out.println("VolatileLong解决了伪共享问题.");
        final long start = System.nanoTime();
        runTest();
        System.out.println("持续时间 = " + (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;
        }
    }

    public final static class VolatileLong {
        public volatile long value = 0L;
        public long p1, p2, p3, p4, p5, p6; // comment out
    }
}

测试结果也给大家粘贴一下

VolatileLong未解决了伪共享问题.所以每次longs[i]都会将其他的longs[i+1]或者longs[i-1]的64位加载进来.
持续时间 = 45158255500
VolatileLong解决了伪共享问题.
持续时间 = 25888840119

差距还是很大的.尤其是在大并发的系统中,可能就是灾难性的.

2、总结

2-1 Java 6

解决伪共享的办法是使用缓存行填充,使一个对象占用的内存大小刚好为64bytes或它的整数倍,这样就保证了一个缓存行里不会有多个对象。《 剖析Disruptor:为什么会这么快?(三)伪共享 》提供了缓存行填充的例子:

public final static class VolatileLong   { 
    public volatile long value = 0L; 
    public long p1, p2, p3, p4, p5, p6; // comment out 
}

2-2 Java 7

因此,JAVA 7下做缓存行填充更麻烦了,需要使用继承的办法来避免填充被优化掉

public class VolatileLongPadding {
    public volatile long p1, p2, p3, p4, p5, p6; // 注释  
}
public class VolatileLong extends VolatileLongPadding {
    public volatile long value = 0L;  
}

把padding放在基类里面,可以避免优化。(这好像没有什么道理好讲的,JAVA7的内存优化算法问题,能绕则绕)。

2-3 JAVA 8

中添加了一个@Contended的注解

import sun.misc.Contended;

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

执行时,必须加上虚拟机参数-XX:-RestrictContended,@Contended注释才会生效。

原文  https://blog.csdn.net/xzknet/article/details/82630919
正文到此结束
Loading...