volatile修饰符

1,181 阅读3分钟

前言

在并发编程当中synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

相关术语定义

CPU的术语定义

MESI协议

为什么需要使用CPU Cache?

  • CPU的频率过快,快到主存更不上,这样在处理器的时间钟周期内,CPU常常需要等待主存,浪费资源。
  • cache的出现就是为了环节CPU与内存之间速度不匹配的问题

CPU->cache->momory

CPU Cache有什么意义?

  1. 时间局限性:如果某个数据被访问,那么在不久的将来它很可能被再次访问
  2. 空间局限性:如果某个数据被访问,那么它相邻的数据很快也可能被访问

缓存一致性(MESI)缓存一致协议 用于保证多个CPU cache之间缓存共享数据的一致性缓存行(Cache line):缓存存储数据的单元。

根据具体的硬件MESI协议的实现是不同的。但是都会有两个专业的配套机制:

  1. flush处理器缓存
  2. refresh处理器缓存

flush处理器缓存
这个的意思是把自己更新的值刷新到缓冲器当中去(或者是主存当中),因为必须要刷到高速缓冲区(或者是主存当中),才有可能在后续通过一些特殊的机制让其他的处理器从自己高速缓冲区或者是主存里读取到最新的值。

refresh处理器缓存
他的意思就是说,处理器中的线程正在读取一个变量值的时候,如果发现其他处理器中的线程更新了这个变量值的话,必须从其他处理器的高速缓冲区(或者是主内存当中),读取这个最新的值并写入自己的高速缓冲区

volatile也就是这个原理一些内存屏障的使用,在底层硬件级别的一个原理其实就是在执行flush和refresh


volatile保证可见性

Java代码如下 instance = new Singleton(); // instance是volatile变量

转变成汇编代码,如下。

0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);

有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,Lock前缀的指令在多核处理器下引发了两件事情.

  • 1)将当前处理器缓存行(Cache Line)写回系统内存(也就是主内存).
  • 2)这个写回内存操作将会使得其他CPU里缓存了该内存地址的数据无效

volatile的两条实现原则

  • 1)Lock前缀指令会引起处理器缓存回写到内存。
  • 2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

volatile的实现机制

通过内存屏障和禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量刷新到主内存
  • 对volatile变量读操作时,会在读操作钱加入一条load屏障命令,从主内存中读取共享变量

volatile的应用场景

volatile不具有原子性,所以不适合用于计数等场景 volatile的使用具有条件

  • 1.对变量的写操作不依赖当前值
  • 2.该变量不包含于其他变量的不变式当中

适合作为标记量,双重检测 eg:

volatile boolean inited = false;
//thread 1
context = loadContext();
inited = false;

//thread 2
while(!inited){
    sleep();
}
doSomethingWithConfig(context);