Volatile是java并发中解决同步问题的一个轻量级方案,通过将变量声明为Volatile后,保证变量能被其他线程可见
原理
为什么Volatile可以保证其他线程可见呢?这得先从java的内存模型说起,java内存规定所有变量都会放在主内存中,但是每个线程需要使用变量的时候,都会将变量的值复制到自己的工作内存中,所以各个线程之间,对变量的操作都是不可见,只有最后完成了,重新将变量写入到主内存中,才会被其他线程可见,这样子就会导致并发的时候,变量的值不一致。
而将变量声明为Volatile后,变量就不会先更新到私有内存,而是直接更新到主内存,并且其他线程读取该共享变量的时候也会直接从主内存获取。
但是Volatile是不能保证原子性的。举一个最经典的例子,再并发情况下对变量进行++操作
csharp
代码解读
复制代码
public volatile int num = 0; public void increase() { num++; } public static void main(String[] args) { final Test test = new Test(); for (int i = 0; i < 10; i++) { new Thread() { public void run() { for (int j = 0; j < 1000;j++) test.increase(); }; }.start(); } }} }
由于num++不是一个原子操作,内存语义主要分为读取变量,赋值,增加值,重新将值重新写入到主内存,所以再多个线程并发的情况下,会有线程读取到老的值,从而重复对变量进行赋值。
而所谓的有序性,其实就是Volatile的另一个特性就是禁止重排序,这主要是jmm和编译器处理器之间约束不同导致的一个问题,也就是我们看到到顺序和编译器处理器处理时的执行顺序是不一样的。 而Volatile就保证这个变量的读写操作的前后执行顺序是不会变化的。
例如我们四个变量执行,我们看到是顺序是这样的
ini
代码解读
复制代码
int y = 1; int z = 3; int x = 2; int c = 4;
但编译器经过优化可能是这样的,编译器只保证最后的结果是跟我们看到顺序执行是一致的。
ini
代码解读
复制代码
int z = 3; int y = 1; int c = 4; int x = 2;
而当我们对某个变量声明为Volatile后
ini
代码解读
复制代码
int y = 1; int z = 3; volatile int x = 2; int c = 4; int r = 4;
编译器会保证他的前后一致,但是不会保证它之前的顺序一致。也就是这样,保证了x一定是在z,y后执行,rc前执行
ini
代码解读
复制代码
int z = 3; int y = 1; volatile int x = 2; int r = 4; int c = 4;
总结
并发的三大概念,可见性,原子性,有序性。Volatile只能保证可见性和部分的有序性。