美文网首页
volatile的内存语义

volatile的内存语义

作者: 是一动不动的friend | 来源:发表于2017-11-25 21:50 被阅读19次

    1.volatile的特性

     a>可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入

     b>原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

    2. volatile 写-读的内存语义

    volatile写的内存语义:

    当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。

    这里会刷新所有的共享变量,不仅仅是volatile域的变量

    volatile读的内存语义:

    当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

    这里会从主内存中读取所有的共享变量,不仅仅是volatile域的变量

    3.volatile内存语义的实现

    volatile重排序规则表

    1>当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

    2>当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

    3>当地一个操作是volatile写,第二个操作是volatile读时,不能重排序。

        为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。

        1.在每个volatile写操作的前面插入一个StoreStore屏障。

        2.在每个volatile写操作的后面插入一个StoreLoad屏障。

        3.在每个volatile读操作的后面插入一个LoadLoad屏障。

        4.在每个volatile读操作的后面插入一个LoadStore屏障。

    指令序列示意图

         如上图:StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。

         另一个volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障。为了保证能正确实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。

    指令序列示意图

        上图LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。

       上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。

    内存屏障的优化:

    class VolatileBarrierExample {

    int a;

    volatile int v1 = 1;

    volatile int v2 = 2;

    void readAndWrite() {

    int i = v1;          //第一个volatile读

    int j = v2;          // 第二个volatile读

    a = i + j;            //普通写

    v1 = i + 1;          // 第一个volatile写

    v2 = j * 2;          //第二个 volatile写

    }

    …                    //其他方法

    }

    指令序列示意图

        上图最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。

       上面的优化针对任意处理器平台,由于不同的处理器有不同“松紧度”的处理器内存模型,内存屏障的插入还可以根据具体的处理器内存模型继续优化。以X86处理器为例,X86不允许很多重排序所以上面的程序如果在X86处理器上进行时,会省略一部分内存屏障。

    相关文章

      网友评论

          本文标题:volatile的内存语义

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