Java でダブルチェック ロック パラダイムが壊れている理由を説明しているこの記事に出くわしました。変数が宣言されている場合、パラダイムは .NET (特に C#) で有効ですvolatile
か?
8 に答える
ダブルチェック ロックは、C# だけでなく Java でも機能するようになりました (Java メモリ モデルが変更され、これが影響の 1 つです)。ただし、正確に取得する必要があります。少しでも混乱すると、スレッドセーフが失われる可能性があります。
他の回答が述べているように、シングルトン パターンを実装している場合は、それを行うためのはるかに優れた方法があります。個人的には、二重チェックのロックを自分で実装するか、「毎回ロックする」コードを実装するかを選択しなければならない状況にある場合、それがボトルネックを引き起こしているという実際の証拠が得られるまで、毎回ロックすることにします。スレッドに関して言えば、シンプルで明らかに正しいパターンは非常に価値があります。
.NET 4.0 には新しい型がありますLazy<T>
。これにより、パターンが間違っているという懸念が取り除かれます。これは、新しい Task Parallel Library の一部です。
MSDN Parallel Computing Dev Center を参照してください: http://msdn.microsoft.com/en-us/concurrency/default.aspx
ところで、.NET 3.5 SP1 のバックポート (サポートされていないと思います) がこちらで入手できます。
C# でのシングルトン パターンの実装では、3 番目のバージョンでこの問題について説明しています。
それは言います:
インスタンス変数を volatile にすると、明示的なメモリ バリア呼び出しと同様に機能する可能性がありますが、後者の場合、専門家でさえどのバリアが必要かについて正確に合意することはできません。私は、専門家が何が正しくて何が間違っているかについて意見が一致しないような状況を避けようとする傾向があります。
著者は、二重ロックは他の戦略よりも機能する可能性が低いため、使用すべきではないと暗示しているようです。
Java よりも (おそらく .Net でも)、シングルトンの初期化のためのダブルチェック ロックは完全に不要であり、壊れていることに注意してください。クラスは最初に使用されるまで初期化されないため、目的の遅延初期化はこれによってすでに達成されています。
private static Singleton instance = new Singleton();
Singleton クラスに、Singleton インスタンスが最初に使用される前にアクセスできる定数のようなものが含まれていない限り、これだけで十分です。
ブール値を使用して(つまり、遅延初期化を回避するためにプリミティブを使用して)機能するようにロックを再確認しました。
ブール値を使用したシングルトンは機能しません。異なるスレッド間で見られる操作の順序は、メモリバリアを通過しない限り保証されません。言い換えると、2番目のスレッドから見て、
created = true
前に実行される可能性がありますinstance= new Singleton();
二重チェックのロックが悪いパターンであるとすべての人が言う理由はわかりませんが、正しく機能するようにコードを変更しないでください。私の意見では、この以下のコードは問題なく動作するはずです。
このコードが Cameron の記事で言及されている問題に悩まされているかどうかを誰かが教えてくれれば、教えてください。
public sealed class Singleton {
static Singleton instance = null;
static readonly object padlock = new object();
Singleton() {
}
public static Singleton Instance {
get {
if (instance != null) {
return instance;
}
lock (padlock) {
if (instance != null) {
return instance;
}
tempInstance = new Singleton();
// initialize the object with data
instance = tempInstance;
}
return instance;
}
}
}
二重チェック ロックに関する多数の実装パターンがある理由がよくわかりません (明らかに、さまざまな言語でコンパイラの特異性を回避するためです)。このトピックに関するウィキペディアの記事では、単純な方法と問題を解決するための可能な方法を示していますが、これほど単純なものはありません (C# の場合):
public class Foo
{
static Foo _singleton = null;
static object _singletonLock = new object();
public static Foo Singleton
{
get
{
if ( _singleton == null )
lock ( _singletonLock )
if ( _singleton == null )
{
Foo foo = new Foo();
// Do possibly lengthy initialization,
// but make sure the initialization
// chain doesn't invoke Foo.Singleton.
foo.Initialize();
// _singleton remains null until
// object construction is done.
_singleton = foo;
}
return _singleton;
}
}
Java では、lock() の代わりに synchronized() を使用しますが、基本的には同じ考え方です。シングルトン フィールドが割り当てられるタイミングに矛盾がある可能性がある場合は、最初にローカル スコープの変数を使用してから、クリティカル セクションを終了する前の可能な限り最後の時点でシングルトン フィールドを割り当ててみませんか? 何か不足していますか?
@michael-borgwardt による、C# および Java では、静的フィールドは最初の使用時に一度だけ初期化されるという議論がありますが、その動作は言語固有です。また、コレクション プロパティ (user.Sessions など) の遅延初期化に、このパターンを頻繁に使用しました。
ブール値を使用して(つまり、遅延初期化を回避するためにプリミティブを使用して)動作するようにロックを再確認しました。
private static Singleton instance;
private static boolean created;
public static Singleton getInstance() {
if (!created) {
synchronized (Singleton.class) {
if (!created) {
instance = new Singleton();
created = true;
}
}
}
return instance;
}