有一种方法可以在不使用volatile
字段的情况下实现它。我将解释一下……
我认为锁内的内存访问重排序是危险的,这样你可以在锁外得到一个不完全初始化的实例。为了避免这种情况,我会这样做:
public sealed class Singleton
{
private static Singleton instance;
private static object syncRoot = new Object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
var temp = new Singleton();
System.Threading.Thread.MemoryBarrier();
instance = temp;
}
}
}
return instance;
}
}
}
理解代码
假设在单例类的构造函数中有一些初始化代码。 如果这些指令在将字段设置为新对象地址之后重新排序,则会导致不完整的实例... 假设该类具有以下代码:
private int _value;
public int Value { get { return this._value; } }
private Singleton()
{
this._value = 1;
}
现在想象一下使用new运算符调用构造函数:
instance = new Singleton()
这可以扩展为以下操作:
ptr = allocate memory for Singleton;
set ptr._value to 1;
set Singleton.instance to ptr;
如果我把这些指令重新排序会怎样:
ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;
这有什么影响吗?如果你只考虑单个线程,没有。但如果你考虑多个线程,有……如果在线程刚执行完 set instance to ptr
后被中断会怎么样:
ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;
这就是内存屏障所避免的事情,它防止了内存访问重排序:
ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
set Singleton.instance to temp;
编程愉快!