1.Volatile 出场
最早见volatile是在单例模式中,只能大概记得防止重排这个点:
可以看到instance需要定义为volatile变量,
到底为啥?我也是一知半解,最近有时间,好好梳理下。
public class SingleInstanceA{ private static volatile SingleInstanceA instance; public static SingleInstanceA getInstance(){ if(instance == null){ synchronize(SingleInstanceA.class){ if(instance == null){ instance=new SingleInstanceA(); } } } } }
2.怎么理解
从java 角度,我一直不能理解这个词,总觉得雾里看花。终于看到一篇从嵌入式/C++角度的文章, 恍然大悟。
文章中是这么描述的:
1.原理作用
Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。“易变”是因为外在因素引起的,像多线程,中断等。
C语言书籍这样定义volatile关键字:
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
简单来说,volatile 告诉 编译器,不要重排指令,每次读取都要去主内存(变量内存地址)中取,不要用当前线程的本地内存(寄存器)。
因此可以保证 volatile的变量修改对于其他线程可见。
SO 可见性保证了。
因为不重排 = 编译器不重排 +处理器不重排
让volatile 不重排,所以设计者们 设计了 一个叫屏障( StoreLoad,LoadLoad,LoadStore)的东西来实现(一个指令类型,跟setnx没什么差别)
针对volatile 变量的读和写设计了一套屏障的规则,保证了变量的前后的指令也是有序的。
so 有序了.
实际上这两种特性是因为volatile的设计和实现来保证的,(别管为什么,就是这么设计的。)
3.怎么使用
volatile 主要是用于修饰变量。
public class volatileTest{
private volatile int arg= 1;
public int val(){ --------线程安全,正确用法
arg=2
}
public int increase(){ --------线程不安全
arg++;(arg+1)
}
public int val( int valb){ --------线程不安全
arg=valb
}
}
volatile 代替synchronized 的场景
public class volatileA{
private volatile int argA;
public int val(){
val=1;
}
}
public class synchronizedB{
private int argB;
public synchronied int val(){
val=1;
}
}