理解volatile用到的知识点:
1、内存模型:CPU硬件有它自己的内存模型,不同的编程语言也有它自己的内存模型。
2、原子性:最小的cpu操作 double 和 long在64位的操作系统中是保持原子性,在32位的操作系统中分两次执行,如果数据类型不支持原子性,则会造成多线程的读取访问数据不一致。
- 如果一组变量总是在相同的锁内进行读写,就可以称为原子的(atomically)读写。假定字段
x
与y
总是在对locker
对象的lock
内进行读取与赋值 - 指令原子性是一个相似但不同的概念:如果一条指令可以在 CPU 上不可分割地执行,那么它就是原子的。
- volatile 关键字只对原子性的数据类型有效。
3、多线程:多线程对一个实例对象的A()和B()方法进行访问操作时候,由于线程访问顺序不同,会引起线程实例对像内部字段读取顺序不一样,导致每次运行的结果不一样。处理这个问题就要给内部字段添加volatile 关键字。
4、编辑器 clr cpu优化:编译器和clr为了提高程序的运行效率会对代码经行优化。这样优化会导致代码顺序发生变化,导致多线程的时候执行的效果不一样。
5、CPU和主存储器的通信模型:包括cpu和 缓冲 寄存器 内存直接的互动。
6、获取语义(acquire semantics):硬件层面就是保证在此之后的所有指令在执行的时候都不能重排到这 个指令之前, 软件层面在此之后的所有操作都不能重排到这个语义(fence)之前
Acquire语义修饰内存读操作(包括内存读取或者读-修改-写操作),倘若一个操作被该修饰符修饰(read-acquire),那么他就能够防止其后面的所有内存读/写操作重排到他的前面。
7、释放语义(release semantics,):Release语义修饰内存写操作(包括内存修改或者读-修改-写操作),倘若一个操作被该修饰符修饰(write-release),那么他就能够防止其前面的所有内存读/写操作重排到他的后面。
volatile
关键字使用范围:
- 引用类型。
- 指针类型(在不安全的上下文中)。 请注意,虽然指针本身可以是可变的,但是它指向的对象不能是可变的。 换句话说,不能声明“指向可变对象的指针”。
- 简单类型,如
sbyte
、byte
、short
、ushort
、int
、uint
、char
、float
和bool
。 - 具有以下基本类型之一的
enum
类型:byte
、sbyte
、short
、ushort
、int
或uint
。 - 已知为引用类型的泛型类型参数。
- IntPtr 和 UIntPtr。
其他类型(包括 double
和 long double 和 long在64位的操作系统中是保持原子性,在32位的操作系统中分两次执行。
)无法标记为 volatile
,因为对这些类型的字段的读取和写入不能保证是原子的。 若要保护对这些类型字段的多线程访问,请使用 Interlocked 类成员或使用 lock
语句保护访问权限。
volatile
关键字只能应用于 class
或 struct
的字段。 不能将局部变量声明为 volatile
。
volatile 关键字
易变性:voldatile关键字首先具有“易变性”,声明为volatile变量编译器会强制要求读内存,相关语句不会直接使用上一条语句对应的的寄存器内容,而是重新从内存中读取。
不可优化:其次具有”不可优化”性,volatile告诉编译器,不要对这个变量进行各种激进的优化,甚至将变量直接消除,保证代码中的指令一定会被执行。
顺序性:最后具有“顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。不过要注意与非volatile变量之间的操作,还是可能被编译器重排序的。
原子性:只对指定数据类型有效。
具有获得语义和释放语义
- 对于Acquire来说,保证Acquire后的读写操作不会发生在Acquire动作之前
- 对于Release来说,保证Release前的读写操作不会发生在Release动作之后