フィールドなしで実装する方法があり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;
違いはありますか?単一のスレッドを考える場合はNOです。はい、複数のスレッドを考えている場合...次の直後にスレッドが中断された場合はどうなりますか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;
ハッピーコーディング!