volatile 定义
Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
volatile实现原理
有volatile变量修饰的共享变量进行写操作的时候,编译器会增加汇编代码LOCK,lock前缀的指令在多核处理器下会引发了两件事情。
将当前处理器缓存行的数据会写回到系统内存。
这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
《java并发编程艺术》第二章对此有详细的介绍。下面结合第三章 Java内存模型来看。
volatile 的内存语义
volatile的内存语义
volatile写的内存语义:
当写一个volatile变量时,JMM会把改线程对应的本地内存中的共享变量值刷新到主内存。
当读取一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存读取共享变量。
volatile内存语义的实现
可以从JMM看如何实现volatile内存语义。在执行程序时为了提高性能,编译器跟处理器经常对指令进行重排序,这些重排序可能会对多线程程序出现内存可见性问题,对于编译器,JMM的编译器重排序规则会禁止编译器对特定类型的编译器重排序,对于处理器,JMM的处理器重排序规则要求java编译器在生成字节码时,会在指令序列插入内存屏障来禁止特定类型的处理器重排序。为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列插入内存屏障来禁止特定类型的处理器重排序。
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。内存屏障可以被分为以下几种类型
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
JMM采用了storeload,这种策略非常保守,但是可以保证跨处理器平台正确的得到volatile内存语义。这里可以理解为优先保证正确性,再追求效率。
参考:http://ifeve.com/volatile/