美文网首页
Volatile禁止指令重排

Volatile禁止指令重排

作者: CodeYang | 来源:发表于2022-10-27 13:10 被阅读0次

    以下截图及相关信息,均来源于马士兵公开课中


    Volatile 禁止指令重排

    CPU 存在乱序执行,Volatile 可以保证禁止指令重排(乱序执行)

    一、Volatile 保证线程之间测试

    测试代码:

    volatile 关键字修饰与否,测试变量是否出现指令重排的结果

    public class T01_Volatile_Rearrangement {
        private static int x = 0, y = 0;
        private static int a = 0, b = 0;
    
        /**
         * 可能出现的排列顺序:
         * a(1),x(0),b(1),y(1)      x:0  y:1
         * a(1),b(1),x(1),y(1)      x:1  y:1
         * a(1),b(1),y(0),x(1)      x:1  y:0
         * <p>
         * b(1),y(0),a(1),x(1)      x:1  y:0
         * b(1),a(1),y(1),x(1)      x:1  y:1
         * b(1),a(1),x(0),y(0)      x:0  y:1
         * <p>
         * 不可能出现的情况为: x:0  y:0
         * 除非出现这种情况:
         * x(0),y(0),a(1),b(1)
         * x(0),y(0),b(1),a(1)
         * y(0),x(0),a(1),b(1)
         * y(0),x(0),b(1),a(1)
         * <p>
         * 结论: x,y的执行在 a,b执行之前;说明了出现指令重排的情况
         *
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            for (long i = 0; i < Long.MAX_VALUE; i++) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                CountDownLatch latch = new CountDownLatch(2);
    
                Thread th1 = new Thread(() -> {
                    a = 1;
                    x = b;
                    latch.countDown();
                });
    
                Thread th2 = new Thread(() -> {
                    b = 1;
                    y = a;
                    latch.countDown();
                });
    
    
                th1.start();
                th2.start();
                latch.await();
                String result = "第" + i + "次(" + x + "," + y + ")";
                if (x == 0 && y == 0) {
                    System.err.println(result);
                    break;
                }
            }
        }
    }
    
    

    运行结果:

    • 当没有 volatile 关键字修饰时, 程序不会停止,因为不可能出现 x = 0 并且 y = 0 的情况;
    • 当有 volatile 关键字修饰时, 程序停止,输出 x = 0 并且 y = 0 的情况。

    产生的原因:

    发生了指令重排,才会出现 x = 0 并且 y = 0 的情况。

    若不发生指令重排,不可能出现 x = 0 并且 y = 0 的情况。

    二、Volatile 是如何禁止指令重排的?

    实现过程

    1. Java代码层面:Volatile 关键字

    2. Java字节码层面:ACC_VOLATITLE

    3. JVM内存屏障:屏障两边的指令不可以重排序,保障有序!

    4. hotspot 实现:

      bytecodeinterpreter.cpp

      int field_offset = cache->f2_as_index();
                 if (cache->is_volatile()) {
                   if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
                     OrderAccess::fence();
                   }
      

      orderaccess_linux_x86.inline.hpp

      inline void OrderAccess::fence() {
         if (os::is_MP()) {
           // always use locked addl since mfence is sometimes expensive
       #ifdef AMD64
           __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
       #else
           __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
       #endif
         }
       }
      

    底层实现两种方式:

    1.内存屏障

    内存屏障sfence mfence lfence等系统原语

    2.锁总线

    内存屏障介绍

    屏障指的是什么?屏障指的是一种特殊的指令(例如:barrier),只要看到 barrier 就不让前后指令交换顺序,这就是使用屏障禁止指令重排。

    屏障在底层角度:从CPU角度,每种CPU的屏障指令是不一样的,

    例如:Intel(英特尔)

    CPU内存屏障,Intel设计得比较简单,总共只有3条指令:

    ①sfence:也就是save fence,写屏障指令。在sfence指令前的写操作必须在sfence指令后的写操作前完成。

    ②lfence:也就是load fence,读屏障指令。在lfence指令前的读操作必须在lfence指令后的读操作前完成。

    ③mfence:在mfence指令前得读写操作必须在mfence指令后的读写操作前完成。

    Java是跨平台的语言,Java怎么保证在不同的平台,都能够实现呢

    JVM定义规范,定义无论是Linux上的JVM,还是Window上的JVM,还是在Intel CPU上的JVM,我只要使用了Volatile 关键词,哪一个都不允许指令交换顺序。

    JVM内存屏障

    无论哪种CPU或者哪种系统上的JVM必须实现这四种屏障;

    LoadLoad屏障:

    对于这样的语句:Load1;LoadLoad;Load2

    在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据读取完毕。

    即: Load1 读取数据完毕后, Load2才能读取数据

    StoreStore屏障:

    对于这样的语句:Store1; StoreStore; Store2

    在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

    即:Store1写入操作完毕后, Store2才能进行写入操作

    LoadStore屏障:

    对于这样的语句: Load1; LoadStore; Store2

    在Store2及后续代码写入操作被刷出前,保证Load1读取的数据读取完毕。

    即:Load1 读取数据完毕后, Store2才能进行写入操作

    StoreLoad屏障:

    对于这样的语句:Store1; StoreLoad; Load2

    在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

    即:Store1写入操作完毕后, Load2才能读取数据

    相关文章

      网友评论

          本文标题:Volatile禁止指令重排

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