volatile 关键字
volatile 的作用
- 使用
volatile
关键字会强制将修改的值立即写入主存。当线程 2 对变量进行修改时,会导致线程 1 的工作内存中缓存变量的缓存无效;线程 1 会重新从主存中读取变量的值。 - 禁止指令重排
指令重排的示例
public static Singleton getSingleton() {
if (instance == null) { //Single Checked
synchronized (Singleton.class) {
if (instance == null) { //Double Checked
// 这一步会出现指令重排
instance = new Singleton();
}
}
}
return instance ;
}
instance = new Singleton();
处会出现指令重排。
这句代码分为三步
- 分配内存地址
- 调用构造方法初始化成员变量
- 将对象指向分配的内存地址。
第 2、3 步会出现指令重排,无法保证 2、3 的执行顺序,可能时 1-2-3、也可能是 1-3-2。
如果在线程执行 1-3 时,另一个线程执行了 if (instance == null)
判断,就会返回一个未初始化的对象。这样会报错。
volatile 的实现
加了 volatile
关键字的汇编代码会多出一个 lock
前缀指令
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏) ,内存屏障会提供3个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。
synchronized 关键字和 volatile 关键字的区别
volatile
关键字是线程同步的轻量级实现,所以volatile
性能肯定比synchronized
关键字要好。volatile
关键字只能用于变量而synchronized
关键字可以修饰方法以及代码块。- 多线程访问
volatile
关键字不会发生阻塞,而synchronized
关键字可能会发生阻塞 volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证volatile
关键字主要用于解决变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程之间访问资源的同步性。