深入理解volatile原理与使用
深入理解volatile原理与使用
Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的,保证不了非原子性操作,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
有volatile变量修饰的共享变量在进行写操作的时候汇编代码中会出现Lock指令
Lock指令在多核处理器下会引发两件事情:
- 将当前处理器缓存行的内容写回到系统内存
- 写回到内存的操作会使在其他CPU里缓存了该内存地址的数据失效,Intel64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。
为了提高处理速度,处理器不直接与内存进行通行,而是将系统内存的数据独到内部缓存(L1,L2或其它)后再进行操作。如果对申明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存中。在多处理器下,为了保证各个处理器中的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存中。
下面的代码给出了一个volatile变量常用的场景,在代码中,设置run变量为volatile,在第一个线程执行完毕后,将run设置为true,而在第二个线程中,首先不停地自旋操作,判断run变量是否为true,然后在进行线程任务。
public class Demo2 { public volatile boolean run = false; public static void main(String[] args) { Demo2 d = new Demo2(); new Thread(new Runnable() { @Override public void run() { for(int i = 1;i<=10;i++) { System.err.println("执行了第 " + i + " 次"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } d.run = true; } }).start(); new Thread(new Runnable() { @Override public void run() { while(!d.run) { // 不执行 } System.err.println("线程2执行了..."); } }).start(); } }