一,概念解释
volatile 关键字指示一个字段可以由多个同时执行的线程修改。
出于性能原因,编译器,运行时系统甚至硬件都可能重新排列对存储器位置的读取和写入。 声明了 volatile 的字段不进行这些优化。
添加 volatile 修饰符可确保所有线程观察易失性写入操作(由任何其他线程执行)时的观察顺序与写入操作的执行顺序一致。 不确保从所有执行线程整体来看时所有易失性写入操作均按执行顺序排序。
二,修饰类型
volatile 关键字可修饰于以下类型的字段:
- 引用类型。
- 简单类型,如sbyte、byte、short、ushort、int、uint、char、float 和 bool。
- 具有以下基本类型之一的 enum 类型:byte、sbyte、short、ushort、int 或 uint。
- 已知为引用类型的泛型类型参数。
- IntPtr 和 UIntPtr。
其他类型(包括 double 和 long)无法标记为 volatile,因为对这些类型的字段的读取和写入不能保证是原子的。 若要保护对这些类型字段的多线程访问,可使用 Interlocked 类成员或使用 lock 语句保护访问权限。
volatile 关键字只能应用于 class 或 struct 的字段。 并且不能将局部变量声明为 volatile。
三,实际运用
通常情况下我们是这样使用单例模式的,代码如下:
/// <summary>
/// 单例
/// </summary>
class Singleton
{
private static Singleton _ins;
public static Singleton Instance
{
get
{
if (_ins == null)
{
_ins = new Singleton();
}
return _ins;
}
}
// 私有化构造
private Singleton() { }
}
若我们程序需要在多线程中执行时,上面这种写法就可能会有问题,一种极限情况就是,两个线程同时进入if (_ins == null)
时,此时会new 两个对象出来,这显然违背了我们使用单例模式的意愿,进而优化出来下面这种单例写法,在多线程中使用
/// <summary>
/// 多线程单例
/// </summary>
class SingletonThread
{
private static volatile SingletonThread _ins;
private static object lockHelper = new Object { };
public static SingletonThread Instance
{
get
{
if(_ins == null)
{
lock (lockHelper)
{
if(_ins == null)
_ins = new SingletonThread();
}
}
return _ins;
}
}
// 私有化构造
private SingletonThread() { }
}