前言
之前的文章对volatile进行了简单的介绍。volatile可以保证变量对所有线程可见,他是通过lock指令实现的。而lock指令在操作系统具体什么作用,之前介绍过,这里不再说了。今天我们将会更加深入的介绍volatile。
volatile的特性
可见性:对于一个volatile变量的读,总是能看到对这个volatile 的变量的最后的写入。(为什么之前说过了,这里不说了)
原子性:对于单个volatile变量的读写具有原子性,但是volatile++这种复合操作不具有原子性。(这个之前说过)
volatile读写建立的happens-before关系
volatile的读写和锁的释放和获取有相同的内存效果。
原本在没有voaltile的情况下,是不能保证1在34之前的,但是因为有了volatile,所以可以保证1在34之前,感觉是不是和锁的感觉有点类似呢。而为什么1在34之前呢。首先根据程序次序规则,1happens-before2,3happens-before4,由因为2happens-before3,所以根据传递性,是不是可以得出1happens-before3,4.
所以我们可以得出,线程A在写这个volatile变量之前所有可见的共享变量值都将立即变得对线程b可见。
1.线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了其对共享变量修改的消息。
2.线程b读一个volatile变量,实质上是线程b接收了之前某个线程发出的在写这个volatile变量之前对共享变量所作的修改消息。
3.线程a写一个volatile变量,随后线程b读这个volatile变量,这个过程实质上就是线程a通过主存向线程b发送消息。(不知道大家还知不知道,并发编程的问题就是,怎么解决线程通信,而解决方案有两种:1.共享内存 2.消息传递。共享内存其实就是通过修改共享内存的公共变量达到隐式传递消息。)
volatile内存语义实现
大家不知道是否还记得,jmm只是向as-if-else靠拢,但是并不是安全按照那个语义来的。也就是单线程也会重排序。那么上面的1怎么happens-before2呢。这就是volatile的另一个特点,会禁止部分的指令重排序。
从上面这个图大家可以看出,当第一个操作时volatile读的时候,之后的操作都不允许重排序,当第二个操作是volatile写的时候,之前的操作,都不允许重排序。当第一个操作时volatile写第二个操作时volatile读的时候,操作不允许重排序。
jmm实现指令禁止重排序,是使用内存屏障,所以volatile也是使用的内存屏障(之间介绍过内存屏障)。
1.volatile写之前插入一个storestore屏障
2.volatile写之后插入一个storeload屏障
3.volatile读之后插入一个loadload屏障
4.volatile读之后插入一个loadstore屏障
volatile写之后插入一个storeload屏障(不同这个为嘛只能防止volatile读/写重排序,而且storeload还是最全能的屏障,具有其他三个屏障的特性)。
volatile的写的消耗会比读大很多,因为写用到了storeload屏障,而这个屏障是四个屏障中性能消耗最大的一个。
volatile禁止重排序的功能,是在jsr-133之后才有的,之前的旧的内存模型没有这个语义。
总结
之前的文章介绍了,volatile的可见性,以及实现。今天主要介绍的是在可见性的基础上,又添加了一些禁止重排序的特性,当前是在一定条件下。这样,volatile如果使用的好,在一些场景下可以替代锁使用。而它的性能消耗可比锁低多了。