volatile的定义及其原理

653 阅读4分钟

volatile的定义

volatile是java语言中一个修饰共享变量的关键词。共享变量是什么?比较典型的例子就是类的成员变量,这就意味着这个关键字你是不能在方法体内修饰声明的局部变量的。那么,为什么需要这样一个关键字?我们知道,共享变量通常是用于提供给多线程访问的,每个线程都有自己的工作内存(一个抽象概念,其实就是寄存器+高速缓存),线程被执行时,会将频繁读取的数据(例如共享变量)先放入自己的工作内存中,线程执行结束之后,会将有改动的缓存数据回写到内存之中。那么在多核处理器线程并发模式下,并行的线程会复制同一份数据的副本到各自的工作内存中,那么每个线程对这个副本的操作对于其他线程都是不可见的,等到各自线程结束回写到内存时,就会出现值和预期不符。
在JSR文档中,可以找到volatile的定义如下:

A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable 如果一个属性被声明volatile。在Java内存模型中,多线程中看到的该值是一致的(也就是内存可见)

volatile实现原理

关于volatile内存可见性,引入《java并发编程艺术》中的解释如下:

在有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码(带有Lock前缀),Lock前缀的指令在多核处理器下会引发了两件事情。
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部 缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的 变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据 写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操 作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一 致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当 处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

需要补充的是,为了保证volatile的内存可见性,编译器本身对volatile变量读写入做了防止重排序,避免多线程环境下将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。

和synchronized区别

其实volatile和synchronized使用上还是有一定相似之处的,所以我们也会认为volatile是轻量级别的synchronized。
不同点在于synchronized是一个非常重的操作,不论是在轻量级锁阶段的自旋解锁还是在重量级锁阶段的线程阻塞阶段,都会耗费很多的cpu资源。与此同时synchronized修饰的是整个区间(临界条件所涵盖的区间),而volatile只是修饰它作用的变量, 同时synchronized通常会引起线程的切换,而volatile则不会。所以,如果能正确使用volatile替换掉synchronized, 性能表现上肯定更好。
接下来,我们来看一个大致例子了解一下区别:

/**
 * @author fibbery
 */
public class VolatileExample {

    public volatile int value = 0;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

}

上述代码在多线程环境下执行上述方法,语义上大致和下面代码相当

/**
 * @author fibbery
 */
public class SynchronizedExample {

    private int value = 0;

    public synchronized void setValue(int value) {
        this.value = value;
    }

    public synchronized int getValue() {
        return this.value;
    }
}

这也是我们为什么称之为轻量级synchronized的原因

参考

java并发编程艺术