シングルトンパターンの3つの年齢

おそらく、静的オブジェクトが現れるとすぐに、シングルトンパターンが現れました。 Smalltalk-80では、ChangeSetがこの方法で作成され、セッション、ステータス、および同様のオブジェクトがさまざまなライブラリに表示されるようになりました。



1994年に、有名な本Design Patternsが出版され、22の中でも一般の人々と、現在シングルトンと呼ばれているヒーローを紹介しました。 彼のC ++実装は次のようにありました。





//.h class Singleton { public: static Singleton* Instance(); protected: Singleton(); private: static Singleton* _instance; } //.cpp Singleton* Singleton::_instance = 0; Singleton* Singleton::Instance() { if(_instance == 0){ _instance = new Singleton; } return _instance; }
      
      





フローについては、この関連性がほとんどない問題を考慮して、著者はそれらについても書きません。 しかし、そのようなクラスの相互の継承のすべての微妙さには多くの注意が払われました。



当然のことですが、それは1995年であり、マルチタスクオペレーティングシステムは遅すぎて誰も困惑することはありませんでした。



いずれにせよ、このコードは老化しません。 宣言するクラスが複数のスレッドから呼び出されない場合は、常にこの実装を使用してください。



1995年、Scott MyersはC ++トリックに関する2冊目の本をリリースしました。 とりわけ、メモリを節約し、コンストラクターがいつ実行されるかを正確に知るために、静的クラスの代わりにシングルトンを使用することを推奨します。



この本で、標準的なシングルトンマイヤーズが登場しました。ここに持ってこない理由はありません。

 class singleton { public: static singleton* instance() { static singleton inst; return &inst; } private: singleton() {} };
      
      







言語の標準をきちんと、簡潔に、そして巧みに打ち負かしました。 関数内の静的なローカル変数は、関数自体が呼び出された場合にのみ呼び出されます。



その後、拡張され、もう少しの操作が禁止されました。



 class CMySingleton { public: static CMySingleton& Instance() { static CMySingleton singleton; return singleton; } // Other non-static member functions private: CMySingleton() {} // Private constructor ~CMySingleton() {} CMySingleton(const CMySingleton&); // Prevent copy-construction CMySingleton& operator=(const CMySingleton&); // Prevent assignment };
      
      





新しいC ++ 11標準によれば、スレッドをサポートするためにこれ以上必要なものはありません。 しかし、すべてのコンパイラーは完全なサポートに対応する必要があります。



その間、少なくとも15年間、最高の頭脳は、言語構文のセルでマルチスレッドシングルトンをキャッチしようとしました。 C ++は、サードパーティのライブラリのないストリームをサポートしていませんでした。そのため、すぐに、ストリームのあるほとんどすべてのライブラリが独自のシングルトンを作成しました。 アレクサンドレスクは彼らに全章を捧げます、国内の開発者は彼のために人生のためではなく死のために苦労しています、そして誰かAndrei Nasonovも長い間実験し、結果として...まったく異なる解決策を提供します。



2004年、マイヤーズとアレクサンドレスクは力を合わせて、ダブルチェックロック付きシングルトンを説明しました。 アイデアは単純です-最初のifでシングルトンが見つからない場合は、ロックを作成して内部で再度チェックします。



それまでの間、トライアルとケースでは、スレッドセーフなシングルトンの問題が他のCライクな言語にcいました。 まず、Javaで、次にC#で。 そして今、John Skeetは幅広いソリューションを提供しており、それぞれに長所と短所があります。 また、Microsoftからも提供されています。



手始めに-ダブルチェックロックの同じオプション。 マイクロソフトは次のように書くことをお勧めします。

 using System; public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new Object(); private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } }
      
      







しかし、Skeetはこのコードが悪いと信じています。 なんで?



-これはJavaでは機能しません。 バージョン1.5より前のJavaメモリモデルは、値を割り当てる前にコンストラクタが完了したかどうかをチェックしませんでした。 幸いなことに、これはもはや関係ありません-Java 1.7はずっと前にリリースされました。Microsoftはこのコードを推奨し、動作することを保証します。

「簡単に破れます。」 括弧で混乱してしまいます-それだけです。

-ロックのため、かなり遅い

-よく食べる



ストリーミングインターフェイスを使用しないオプションもありました。



特に、読み取り専用フィールドを介した既知の実装。 Skeet(およびMicrosoft)によると、これは最初の注目すべきオプションです。次のようになります。



 public sealed class Singleton { private static readonly Singleton instance = new Singleton(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return instance; } } }
      
      









このオプションはスレッドセーフでもあり、読み取り専用フィールドの奇妙なプロパティに基づいています-それらはすぐに初期化されず、最初の呼び出しで初期化されます。 素晴らしいアイデアであり、著者自身がそれを使用することをお勧めします。



この実装には欠点がありますか? もちろん、はい:



-クラスに静的メソッドがある場合、それらが読み取り専用と呼ばれると、フィールドは自動的に初期化されます。

-コンストラクターは静的のみです。 これはコンパイラの機能です。コンストラクタが静的でない場合、型はbeforefieldinitとしてマークされ、読み取り専用は静的なものと同時に作成されます。

-いくつかの関連するシングルトンの静的コンストラクターは、意図せずに相互にループする可能性があり、何も助けず、誰も保存しません。



最後に、ネストされたクラスを持つ有名な遅延実装。

 public sealed class Singleton { private Singleton() { } public static Singleton Instance { get { return Nested.instance; } } private class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton instance = new Singleton(); } }
      
      







その欠陥は、ネストされたクラスを使用する他のコードと同じです。



C#の最近のバージョンでは、System.Lazyクラスが登場しました。これらはすべてカプセル化されています。 そのため、実装はさらに短くなりました。

 public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
      
      







読み取り専用の実装、ネストされたクラスのバリアント、および遅延オブジェクトの形式での単純化は、ストリームでは機能しないことが簡単にわかります。 代わりに、通訳者を「欺く」言語の構造そのものを使用します。 これは、ストリームで機能するダブルロックとの最も重要な違いです。



言語を「欺く」のは良くないのはなぜですか? そのような各「ハック」は非常に簡単に誤って破られるためです。 そして、他の言語で書く人々から彼に利益がないので-そして、パターンは普遍性を暗示します。



個人的には、フローの問題は標準的な方法で解決すべきだと思います。 C#には、マルチスレッドを操作するための多くの組み込みクラスとキーワード全体があります。 コンパイラを「だます」のではなく、標準のツールを使用しないのはなぜですか。



先ほど言ったように、ロックは最良の解決策ではありません。 実際には、コンパイラはそのようなロック(obj)を展開します。



 lock(this) { // other code }
      
      







このコードのようなもの:



 Boolean lockTaken = false; try { Monitor.Enter(this, ref lockTaken); // other code } finally { if(lockTaken) Monitor.Exit(this); }
      
      







ジェフリー・リヒターは、このコードは非常に失敗したと考えています。 まず、試行は非常に遅いです。 第二に、クラッシュしようとすると、コードに何か問題があります。 そして、2番目のスレッドが実行を開始すると、エラーが再び発生する可能性が高くなります。 したがって、彼は通常のフローにMonitor.Enter / Monitor.Exitを使用し、アトミック操作用にシングルトンを書き換えることを求めています。 このように:



 public sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if(instance != null) return instance; Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance; } } }
      
      







C#標準ではコンパイラが最初に変数を作成してから割り当てる必要があるため、一時変数が必要です。 その結果、インスタンスがもはやヌルではないことが判明する場合がありますが、シングルトンの初期化はまだ完了していません。 Jeffrey RichterによるC#を介したCLRの第29章「有名なダブルチェックロックテクニック 」の同様のケースの説明を参照してください。



したがって、場所とダブルロックがありました



このオプションは、マルチスレッドの場合に使用します 。 それは単純で、文書化が不十分なものは何もせず、破ることが難しく、原子操作がある言語に簡単に転送されます。



All Articles