本文仅为个人参考众多文章后的理解,如有错误请指正,红色内容必看。
volatile使用:
private volatile int intValue;
private volatile List<string> strValues;
private volatile FormWindowState style;
private volatile IntPtr mainPtr;
private void button8_Click(object sender, EventArgs e)
{
lock (this)
{
intValue++;
strValues.Add("1");
style = FormWindowState.Minimized;
mainPtr = Handle;
}
}
volatile使用说明:
- volatile无法保证线程安全!!!
- volatile只能修饰字段,不能修饰属性、局部变量。
- volatile可用于以下类型的字段
- 引用类型
- 指针类型(C#语言支持指针类型,在不安全的上下文中)
- 类型为sbyte、byte、short、ushort、int、uint、char、float和bool,并非是值类型。
- 已知为引用类型的泛型类型参数。
- 基类为byte、sbyte、short、ushort、int 或 uint的枚举类型(枚举默认继承自int类型,但可以枚举设置其基类)。
- IntPtr和UInPtr类型。
- 多线程操作volatile修饰字段的代码片段应该使用lock,且提供给lock的对象在不同的代码片段中应该为同一个引用对象。
volatile原理说明:
- MSDN说明:
volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。
-
MSDN翻译:
CLR为了效率会进行优化,会允许线程对变量进行本地缓存等优化操作,当线程操作完后回将变量回写到内存中。当多线程同时执行时会出现变量访问不一致情况,使用volatile可以避免这种优化,保证所有线程对变量的读写都在内存上,保证了所有处理访问该字段都是最新值。
volatile为什么不是线程安全
MSDN上有这么一句话:volatile 修饰符通常用于由多个线程访问但不使用 lock 语句对访问进行序列化的字段。
字段为了线程安全可以使用属性在set和get方法中采用lock语句对字段实现线程安全,使用了volatile只是对字段原子性和可见性进行了保证,但没有保证线程对字段的操作性是有序性。即使在set和get方法中使用lock,依然无法保证对num的修改是线程安全的,因为对num操作的代码依然是线程非安全的。
例如:A和B线程同时对volatile修饰的int类型变量num进行加1操作,当A线程读取num的值后B线程读取num的值后加1赋值给num,线程A对num也加1后赋值给num。虽然A和B读取的num值都是最新的,但是对num+1的操作无法保证原子性和有序性,B先赋值给num后成为num+1,A再次进行写入依然是num+1。
线程安全
原子性、可见性、有序性。
原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作。
可见性:一个线程对主内存的修改可以及时被其他线程观察到。
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。