volatile是什么
volatile是 java提供的一种轻量级同步机制。volatile变量并不保证原子性,因此volatile并不保证线程安全。
volatile关键字有两个作用
1.禁止指令重排序
2.保证可见性
什么是指令重排
jvm在编译代码时,或者cpu在执行jvm字节码时,会对现有指令进行重排序。重排序的目的,是在不影响执行结果的情况下,提高程序运行效率。但是这里的不改变结果是指单线程的情况下,但是对于多线程情况下就会出现共享资源的同步问题。
volatile是如何实现上述两条作用的
原因在于jvm帮助我们在volatile变量的读写操作前后插入了指令屏障(也叫栅栏,具体指令屏障的定义及分类可自行google),内存屏障让volatile读写操作前后的操作禁止和volatile读写操作进行重排序。
volatile关键字为什么不能保证原子操作
比如 ,我们知道i++操作是不具备原子性的,被volatile关键字修饰的i,进行i++操作 这个操作jvm指令可以分为3步
1.从内存中读取i的值到工作内存
2.i+1
3.将修改过的工作内存中的值刷新到主内存
加入有两个线程,线程1,读取了i的值到工作内存然后修改了i的值为10,此时cpu时间片用完了让出了cpu,线程1被阻塞;此时线程2得到了cpu时间片读取i为10、修改、并将i的值刷新回主存,此时主存中的i为11,然后线程1继续执行将i的值刷新回主存,主存中的值为11。而我们期望的值为12。
volatile关键字的典型应用场景-双重检测单例模式
class Singleton3 {
private volatile static Singleton3 singleton;
private Singleton3(){
}
public static Singleton3 getSingleton(){
if(singleton==null){
synchronized (Singleton3.class){
if(singleton==null){
//伪代码
// 1.为新对象分配内存空间
//3.将 singleton3指向 新对象的内存地址
// 2.初始化对象
singleton=new Singleton3();
}
}
}
return singleton;
}
}
双重检测单例模式之所以不安全就是因为,可能会发生指令冲排序,而volatile关键字禁止指令重排序的功能正好解决了这个问题
上面伪代码中正常执行的顺序应该是1,2,3 。而进行指令冲排序后可能的执行顺序变为1,3,2这是因为2,3两步指令重排单线程下并不影响结果。
假如有线程A和线程B,线程A走到
singleton=new Singleton3();
这行代码时发生了指令重排序,此时走到了第3步
3.将 singleton3指向 新对象的内存地址
而第二步
// 2.创建新对象
还未执行。此时线程B走到第一次判断
if(singleton==null)
由于已经分配了内存地址,因此判断为假(我们知道引用类型的对象实际上是存了一个引用在栈里,那么肯定不会判断singleton为空了),此时线程
B拿一个还没创建好的对象去操作,那么肯定是会出问题的。