有一种方法可以在没有volatile
字段的情况下实现它。我来解释一下...
我认为这是危险的锁内的内存访问重新排序,这样您就可以在锁外获得一个未完全初始化的实例。为了避免这种情况,我这样做:
public sealed class Singleton
{
private static Singleton instance;
private static object syncRoot = new Object();
private Singleton() {}
public static Singleton Instance
{
get
{
// very fast test, without implicit memory barriers or locks
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
var temp = new Singleton();
// ensures that the instance is well initialized,
// and only then, it assigns the static variable.
System.Threading.Thread.MemoryBarrier();
instance = temp;
}
}
}
return instance;
}
}
}
理解代码
想象一下 Singleton 类的构造函数里面有一些初始化代码。如果在使用新对象的地址设置字段后重新排序这些指令,那么您有一个不完整的实例...假设该类具有以下代码:
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;
-- thread interruped here, this can happen inside a lock --
set ptr._value to 1; -- Singleton.instance is not completelly initialized
这就是内存屏障通过不允许内存访问重新排序来避免的:
ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
-- memory barrier... cannot reorder writes after this point, or reads before it --
-- Singleton.instance is still null --
set Singleton.instance to temp;
快乐编码!