volatile是Java提供的一种轻量级的同步机制。
并发编程中三个重要特性:
- 原子性:一个操作中就是cpu不可以在中途暂停然后再调度;要么全部成功,要么全部失败。
非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。
由Java内存模型来直接保证的原子性变量操作包括read、load、use、assign、store和write六个,大致可以认为基础数据类型的访问和读写是具备原子性的。
- 可见性:当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的。
普通变量与volatile变量的区别:volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新。
- 有序性:即程序执行的顺序按照代码的先后顺序执行。
Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。
Synchronized & volatile比较
synchronized: 具有原子性,有序性和可见性;
volatile:具有有序性和可见性
Volatile原理
在JVM底层volatile是采用“内存屏障”来实现的。volatile关键字,会使用lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
(1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
(2)它会强制将对缓存的修改操作立即写入主存;
(3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
Volatile特性
当一个变量定义为volatile之后,它将具备:
- 保证此变量对所有线程的可见性
- 禁止指令重排序优化
Volatile使用场景
应用volatile变量的三个原则:
(1)写入变量不依赖此变量的值,或者只有一个线程修改此变量
(2)变量的状态不需要与其它变量共同参与不变约束
(3)访问变量不需要加锁
volatile最适合用的场景是一个线程修改被volatile修饰的变量,其他多个线程获取这个变量的值。
/**
单例模式
*/
public class Singleton{
//使用volatile来保证,singleton
private volatile static Singleton singleton;
public static Singleton getSingleton(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return instance;
}
}
instance = new TestInstance();可以分解为3行伪代码
a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
重排序可能为a-b-c或者a-c-b。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。
使用了 volatile 之后,禁止指令重排序。按照a-b-c的顺序来执行。