Java-volatile线程访问共享变量(二)-XXOO

一、volatile的特性

1.volatile可见性;对一个volatile的读,总可以看到对这个变量最终的写;

2.volatile原子性;volatile对单个读/写具有原子性(32位Long、Double),但是复合操作除外,例如i++;

3.JVM底层采用“内存屏障”来实现volatile语义;

二、volatile与happens-before

示例:

class VolatileTest1 {

        int i = 0;
        volatile boolean flag = false;
        // Thread A
        public void write(){
            i = 2;              //1
            flag = true;        //2
        }
        // Thread B
        public void read(){
            if(flag){                                   //3
                System.out.println("---i = " + i);      //4
            }
        }
    }

依据happens-before原则,就上面程序得到如下关系:

1.依据happens-before程序顺序原则:1 happens-before 2、3 happens-before 4;

2.根据happens-before的volatile原则:2 happens-before 3;

3.根据happens-before的传递性:1 happens-before 4 操作1、操作4存在happens-before关系,那么1一定是对4可见的。

volatile除了保证可见性外, 还有就是禁止重排序。所以A线程在写volatile变量之前所有可见的共享变量,在线程B读同一个 volatile变量后,将立即变得对线程B可见。

三、volataile的内存语义及其实现

在JMM中,线程之间的通信采用共享内存来实现的。volatile的内存语义是:

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

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

所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。

volatile的内存语义是如何实现?

对于一般的变量则会被重排序,而对于volatile则不能,这样会影响其内存语义,所以为了实现volatile的内存语义JMM会限制重排序。

其重排序规则如下:

1.如果第一个操作为volatile读,则不管第二个操作是啥,都不能重排序。这个操作确保volatile读之后的操作不会被编译器重排序到volatile读之前;

2.当第二个操作为volatile写是,则不管第一个操作是啥,都不能重排序。这个操作确保volatile写之前的操作不会被编译器重排序到volatile写之后;

3.当第一个操作volatile写,第二操作为volatile读时,不能重排序。

volatile的底层实现是通过插入内存屏障,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,

所以,JMM采用了保守策略。如下:

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

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

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

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

StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。

StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。

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

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

示例

class VolatileTest2 {
        int a = 0;
        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写
        }
    }

以上

猜你喜欢

转载自blog.csdn.net/qq_35731570/article/details/111035189