volatile变量特性

volatile变量特性--摘自《Java并发编程的艺术》


volatile变量自身具有下列特性:
1.可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
2.原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。




从内存语义的角度来说,volatile与锁有相同的效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。




volatile写-读的内存语义
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。




volatile写和volatile读的内存语义做个总结:


线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所在修改的)消息。
线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。




内存屏障(Memory Barrier)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。
内存屏障可以被分为以下几种类型
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,确保Load1数据装载先于Store2及其后续的存储指令刷新到内存。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,确保Store1数据对其他处理器变得可见(刷新到内存)先于Load2及其后续所有装载
指令的装载。该指令会使屏障之前的所有内存访问指令(读或写)完成之后,才执行屏障之后的内存访问指令。这个屏障是个万能屏障,兼具其它三种内存屏障的功能。执行该屏障开销很贵,因为处理器需要将写缓冲区中所有数据刷新到内存中。




volatile内存语义的实现
重排序包含编译器重排序和处理器重排序,为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:
是否能重排序 第二个操作
第一个操作 普通读/写  volatile读  volatile写
普通读/写                     NO
volatile读  NO           NO              NO
volatile写           NO              NO


举例来说,第三行最后一个单元格的意思是:在程序顺序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。


从上表我们可以看出:


当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:


在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
上述内存屏障插入策略非常保守,但它可以保证在任意处理器平台,任意的程序中都能得到正确的volatile内存语义。




由于volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上,锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。

猜你喜欢

转载自blog.csdn.net/u013038630/article/details/79976662