并发编程-Volatile的伪共享和重排序

上一篇 << 下一篇 >>>CAS无锁模式及ABA问题


Volatile的伪共享问题

CPU每次均会以固定长度读取,一般为64bit,导致就算只改了A,也会把其他没改的B-K一起读取,降低了效率

并发编程-Volatile的伪共享和重排序_第1张图片
并发编程-Volatile的伪共享和重排序_第2张图片

Volatile的伪共享解决办法

/**
 * 解决办法:【数据填充】
 * JDK6中,定义p1-6,加上value,一共占用56个字节 ,在加上VolatileLong类中头占用8个字节一共就是占用64个字节。
 * public final static class VolatileLong{
 *     public volatile long value = 0L;
 *     public  long p1, p2, p3, p4, p5, p6;
 * }
 * jdk7中,写个类单独继承方
 * public final static class VolatileLong extends AbstractPaddingObject {
 *     public volatile long value = 0L;
 * }
 * public class AbstractPaddingObject {
 *     public  long p1, p2, p3, p4, p5, p6;
 * }
 * jdk8中,使用注解@sun.misc.Contended,启动的时候需要加上该参数-XX:-RestrictContended
 * ConcurrentHashMap中就使用了此注解
 * @sun.misc.Contended static final class CounterCell {}
 *
 */
public class FalseShareTest implements Runnable {
    // 定义4和线程
    public static int NUM_THREADS = 4;
    // 递增+1
    public final static long ITERATIONS = 500L * 1000L * 1000L;

    private final int arrayIndex;
    // 定义一个 VolatileLong数组
    private static VolatileLong[] longs;
    // 计算时间
    public static long SUM_TIME = 0l;

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

    public static void main(final String[] args) throws Exception {
        for (int j = 0; j < 10; j++) {
            System.out.println(j);
            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();
            final long end = System.nanoTime();
            SUM_TIME += end - start;
        }
        System.out.println("平均耗时:" + SUM_TIME / 10);
    }

    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 FalseShareTest(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;
        }
    }
    @sun.misc.Contended
    public final static class VolatileLong  {
//        extends AbstractPaddingObject
        // 8个字节 对象占用8个字节
        public volatile long value = 0L;
//        public long p1, p2, p3, p4, p5, p6;
//         48+ 64
    }
}

Volatile解决重排序问题

  • 重排序:编译器和处理器为了提高并行的效率会对代码执行重排序,单线程程序执行结果不会发生改变的,也就是as-ifserial语义,但在多线程情况下就会存在问题。
/**
 * thread1和thread2在单线程情况下重排序都没问题,但在多线程下就存在重排序的问题:
 * 第856750次(0,1)
 * 第856751次(0,1)
 * 第856752次(0,0)
 * 解决办法:使用volatile或手动插入屏障
 * UnSafeUtils.getUnsafe().loadFence()--读屏障
 * UnSafeUtils.getUnsafe().storeFence();--写屏障
 *
 *
 **/
public class ReorderThread {
    // 全局共享的变量
    private /*volatile*/ static int a = 0, b = 0;
    private /*volatile*/ static int x = 0, y = 0;


    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        while (true) {
            i++;
            a = 0;
            b = 0;
            x = 0;
            y = 0;
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 1;
                    //插入一个内存写屏障
                    UnSafeUtils.getUnsafe().storeFence();
                    x = b;

                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    //插入一个内存写屏障
                    UnSafeUtils.getUnsafe().storeFence();
                    y = a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("第" + i + "次(" + x + "," + y + ")");
            if (x == 0 && y == 0) {
                break;
            }
        }
    }
}

内存屏障解决重排序
1.写内存屏障:在指令后插入Stroe Barrier ,能够让写入缓存中的最新数据更新写入主内存中,让其他线程可见。
2.读内存屏障:在指令前插入load Barrier ,可以让告诉缓存中的数据失效,强制
读取主内存,让cpu缓存与主内存保持一致,避免缓存导致的一致性问题。

双重检验锁的单例也应该加上volatile

/**
 * 双重检验锁的单例也应该加上volatile
 *
 * new Singleton03()完成的动作:
 * 1.分配对象的内存空间memory=allocate();
 * 2.调用构造函数初始化
 * 3.将对象复制给变量
 *
 * 第二步和第三步流程存在重排序,将对象复制给变量,在执行调用构造函数初始化,导致另外一个线程获取到该对象不为空,但是该改造函数没有初始化,所以就报错了
 *
 */
public class Singleton03 {
    private static volatile Singleton03 singleton03;

    public static Singleton03 getInstance() {
        // 第一次检查
        if (singleton03 == null) {
            //第二次检查
            synchronized (Singleton03.class) {
                if (singleton03 == null) {
                    singleton03 = new Singleton03();
                }
            }
        }
        return singleton03;
    }

    public static void main(String[] args) {
        Singleton03 instance1 = Singleton03.getInstance();
        Singleton03 instance2 = Singleton03.getInstance();
        System.out.println(instance1==instance2);
    }
}

Volatile和synchronized区别?

a.Volatile保证线程可见性,当工作内存中副本数据无效之后,主动读取主内存中数据,但是并不能保证原子性,Synchronized是保证线程的原子性
b.Volatile可以禁止重排序的问题,底层使用内存屏障。
c.Volatile不会导致线程阻塞,不能够保证线程安全问题,synchronized 会导致线程阻塞能够保证线程安全问题,执行效率较低。
总体而言volatile关键字在某些情况下性能要优于synchronized


相关文章链接:
<<<多线程基础
<<<线程安全与解决方案
<<<锁的深入化
<<<锁的优化
<< << << << << << << << << << <<<线程池
<<<并发队列
<< << << << <<<如何优化多线程总结

你可能感兴趣的:(并发编程-Volatile的伪共享和重排序)