C#単例モード整理

4213 ワード

参照先:
  • 《Implementing the Singleton Pattern in C#》
  • 『CLR via C#(第4版)』
  • 方式1.非スレッドセキュリティ
     
    public class Singleton
    {
        private static Singleton instance = null;
    
        private Singleton() { }
    
        public static Singleton GetInstance()
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
    
            return instance;
        }
    }

    両方のスレッドが「if(instance=null)」を実行し、結果がtrueになり、2つのスレッドがそれぞれインスタンスを作成する可能性があります.
    (if(instance=null)'を実行する前にインスタンスが作成された可能性がありますが、memory modelでは、他のスレッドがinstanceの値が変更されたことをタイムリーに発見できることは保証されません.(メモリフェンス、memory barrier)
     
     
    方式2.単純なスレッドセキュリティ
     
    public sealed class Singleton
    {
        private static readonly object s_lock = new object();
    
        private static Singleton instance = null;
    
        private Singleton() { }
    
        public static Singleton GetInstance()
        {
            lock (s_lock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
    
                return instance;
            }
        }
    }

    lockはメモリフェンスの影響を除去します.
     
    CLRでは、どのロックメソッドの呼び出しも完全なメモリフェンスを構成し、フェンスの前に書き込まれた変数はフェンスの前に完了しなければならない.フェンスの後の変数の読み取りは、フェンスの後で開始する必要があります.
    欠点:instanceを取得するたびにロックを取らなければならず、性能が低下します.
     
    方式3.デュアルロックによるスレッドセキュリティの実現
     
    public class Singleton
    {
        private static object s_lock = new object();
    
        private static Singleton instance = null;
    
        private Singleton() { }
    
        public Singleton GetInstance()
        {
            if (instance == null)
            {
                Monitor.Enter(s_lock);
    
                if (instance == null)
                {
                    Singleton temp = new Singleton();
                    Volatile.Write(ref instance, temp);
                }
            }
    
            return instance;
        }
    }

    >Javaでは信頼できません.JVMはGetInstanceメソッドの開始時にinstanceの値をCPUレジスタに読み込む.2番目のif文を評価すると、レジスタが直接クエリされ、2番目のif文が常にtrueを得るようになります.
     
    >なぜVolatileを使うのかInstance=new Singleton()ではなくWrite?
    Instance=new Singleton()を使用する場合、コンパイラはまずSingletonにメモリを割り当て、instanceにリファレンスを割り当ててからコンストラクタを呼び出すことができます.これにより、呼び出しコンストラクタが完了する前に、別のスレッドがGetInstanceを呼び出し、この未構築のSingletonオブジェクトを使用することができます.Volatile.Writeはtempの参照がコンストラクタが終了した後にのみinstanceに付与されることを保証します.
     
    方式4.ロックされていないスレッドは安全で、遅延なしで作成されます.
    public class Singleton
    {
        private static readonly Singleton instance = new Singleton();
    
        private Singleton() { }
    
        public static Singleton GetInstance()
        {
            return instance;
        }
    }

    欠点:クラスに最初にアクセスしたメンバーは、タイプコンストラクタを呼び出してインスタンスを作成します.
     
     
    方式5.作成の遅延
     
    public class Singleton
    {
        private Singleton() { }
    
        public static Singleton GetInstance()
        {
            return Nested.instance;
        }
    
        private class Nested
        {
            internal static readonly Singleton instance = new Singleton();
        }
    }

    方式6.Lazy
    public class Singleton
    {
        private static Lazy<Singleton> instance
            = new Lazy<Singleton>(() => new Singleton(), true);
    
        private Singleton() { }
    
        public Singleton GetInstance()
        {
            return instance;
        }
    }

     
     
     
    どちらを選ぶか分からない場合は、方法6を使います.