美文网首页RxJavaJava服务器端编程编程语言爱好者
并发编程-Volatile的伪共享和重排序

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

作者: 迦叶_金色的人生_荣耀而又辉煌 | 来源:发表于2020-11-27 07:22 被阅读0次

    上一篇 <<<Volatile解决JMM的可见性问题
    下一篇 >>>CAS无锁模式及ABA问题


    Volatile的伪共享问题

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

    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


    相关文章链接:
    多线程基础
    线程安全与解决方案
    锁的深入化
    锁的优化
    Java内存模型(JMM)
    Volatile解决JMM的可见性问题
    CAS无锁模式及ABA问题
    Synchronized锁
    Lock锁
    AQS同步器
    Condition
    CountDownLatch同步计数器
    Semaphore信号量
    CyclicBarrier屏障
    线程池
    并发队列
    Callable与Future模式
    Fork/Join框架
    Threadlocal
    Disruptor框架
    如何优化多线程总结

    相关文章

      网友评论

        本文标题:并发编程-Volatile的伪共享和重排序

        本文链接:https://www.haomeiwen.com/subject/ikzziktx.html