使用volatile解决可见性问题及阻止指令重排序

使用volatile解决可见性问题及阻止指令重排序

当一个数据被多个线程共享,每个线程都会拷贝一份到线程的缓存中,如果线程没有空闲资源用来读取主存来刷新缓存, 就会产生内存可见性问题,即共享数据彼此不可见

volatile 解决可见性问题

volatile 关键字的作用之一,是系统每次使用被它修饰过的变量时都时直接从内存中提取,而不是从缓存 Cache 中提取,同时对于该变量的更改会马上刷新回主存,使得各个线程取出的值相同,这里的 Cache 可以理解为线程的工作内存。

volatile 可以解决可见性问题,但无法解决原子性问题
(synchronized 或其他的加锁,也能保证内存的可见性,但实现方式略有不同,volatile 的使用成本更低,因为它不会引起线程上下文的切换和调度。

volatile 原理

对于 volatile 修饰的变量的写操作会有一行以 lock 作为前缀的汇编代码。这个指令在多核处理器下会引发两件事,

  1. 将当前处理器缓存行的数据写回到主内存;
  2. 这个写回内存的操作会使其他在 CPU 里缓存了该内存地址的数据无效(禁用缓存)
    lock 前缀的指令会锁住系统总线或者缓存,目的就是保证在同一时间只有一个 CPU 可以修改数据。

volatile 解决指令重排序问题

volatile 的另外一个作用就是阻止指令重排序
JVM 会在不改变数据依赖的前提下对指令进行任意排序以提高程序性能。如果不存在数据依赖性,处理器也可以改变语句对应机器指令的执行顺序;

volatile 通过插入内存屏障来实现阻止指令重排序

happens-before 原则:它保证了程序的有序性,它规定如果两个操作的执行顺序无法从 happens-before 原则中推出来,那么它们就不能保证有序性,可以随意进行重排序:

总结: volatile 可以保证可见性和有序性,但不能保证原子性.