跳到主要内容

volatile 关键字

volatile 的作用

  1. 使用 volatile 关键字会强制将修改的值立即写入主存。当线程 2 对变量进行修改时,会导致线程 1 的工作内存中缓存变量的缓存无效;线程 1 会重新从主存中读取变量的值。
  2. 禁止指令重排

指令重排的示例

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(); 处会出现指令重排。

这句代码分为三步

  1. 分配内存地址
  2. 调用构造方法初始化成员变量
  3. 将对象指向分配的内存地址。

第 2、3 步会出现指令重排,无法保证 2、3 的执行顺序,可能时 1-2-3、也可能是 1-3-2。

如果在线程执行 1-3 时,另一个线程执行了 if (instance == null) 判断,就会返回一个未初始化的对象。这样会报错。

volatile 的实现

加了 volatile 关键字的汇编代码会多出一个 lock 前缀指令

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏) ,内存屏障会提供3个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。

synchronized 关键字和 volatile 关键字的区别

  • volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 关键字要好。
  • volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。
  • 多线程访问 volatile 关键字不会发生阻塞,而 synchronized 关键字可能会发生阻塞
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证
  • volatile 关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性