Singleton


デザインモードとは?


プロジェクトを作成するときに、重複する問題をどのように解決するかの構造をモデル化し、設計者が迅速に正しい設計を作成するのに役立ちます.デザインパターンのカテゴリは、生成、構造、行動の3つに分けられます.

モノトーンパターンの特徴


作成モードに対応するモノトーンモードでは、クラス内のインスタンス数を制御する必要があります.たとえば、共有リソースへのアクセス(データベース、ファイル)を制御する必要があります.グローバル・アクセスを可能にするインスタンスの安定性(インスタンスの問題を他の場所で修正する)を保証する必要がある場合に使用される設計モードは、この2つの問題のうち1つの問題しか解決できない場合に使用される有名な設計モードです.シングルホイールオブジェクトには複数の機能があるため、SRP違反ではなく、前述した2つの問題を解決することができる.
モノトーンインプリメンテーションのコアは2つあります.
  • デフォルトジェネレータは、他のオブジェクトにモノトーンオブジェクトを作成しないようにプライベート化する必要があります.
  • 生成者などのキャラクタに代わる静的メソッドを作成すべきである.このメソッドでは、インスタンスが作成されていない場合は、新しいインスタンスを作成して返さなければなりません.インスタンスが作成されている場合は、インスタンスを返さなければなりません.
  • Early Initialization


    この実装は、クラスのロード時にのみインスタンスを作成するため、インスタンスの作成を保証し、getInstance()はインスタンスのみを返します.しかし、クラス内に他の静的メソッドが存在し、メソッドが呼び出された場合、クラスの1つの欠点は、ロードが進むにつれてインスタンスが生成され、プログラムが終了するまでメモリに残ることである.(スタティック領域のメモリはヒープ領域のメモリとは異なり、GCは自動的に解放されません.)
    public class EagerSingleton {
        private static EagerSingleton instance = new EagerSingleton();
        
        private EagerSingleton() {
        }
        
        public static EagerSingleton getInstance() {
            return instance;
        }
    }

    Lazy Initialization (single-threaded)


    次のモノトーン方式では、getInstance()のインスタンスが作成されます.呼び出し時にインスタンスが作成されていない場合は、ユーザーがインスタンスを要求したときにのみインスタンスの作成を開始する新しいインスタンスを作成できます.これにより、以前はホストメモリの浪費を招く可能性があった初期の方法が改善されますが、2つ以上のスレッドがif文を通過すると、複数のインスタンスが作成されます.したがって,これはマルチスレッド環境では使用できない実装である.
    public class LazyInitializedSingleton {
        private static LazyInitializedSingleton instance;
        
        private LazyInitializedSingleton() {}
        
        public static LazyInitializedSingleton getInstance(){
            if(Objects.isNull(instance)) {
                instance = new LazyInitializedSingleton();
            }
            return instance;
        }
    }

    Double Checked Locking (thread-safe)


    次の方法は、前に見たlazy方式のモノトーン実装を、マルチスレッドで使用可能な形式に改良することである.主な特性は、最初のif文のインスタンスがすでに存在する場合、すぐにそのインスタンスを返すことです.そうしないと、synchronizedで複数のスレッドに順次アクセスし、各スレッドで2番目のif文を介してインスタンスがすでに存在することを確認することができます.ロックが体現しています.
    public class DoubleCheckedSingleton {
        private static volatile DoubleCheckedSingleton instance = null;
    
        private DoubleCheckedSingleton() {}
    
        public static DoubleCheckedSingleton getInstance() {
            if (Objects.isNull(instance)) {
                synchronized (DoubleCheckedSingleton.class) {
                    if (Objects.isNull(instance)) {
                        instance = new DoubleCheckedSingleton();
                    }
                }
            }
            return instance;
        }
    }

    +volatileとマルチスレッド


    Double Checked Locking方式では、volatileというキーワードがなくても通常はスレッドが安全に動作するが、Javaのスレッドが使用するメモリ形式では、volatileが実現していない上のモノトーンが複数のインスタンスを生成するリスクがある.

    スレッドは、メインメモリから取得した値を各スレッドが有するワークメモリに移行し、格納した値を実行エンジンに送信するプロセスとして読み出し、この逆シーケンスプロセスに書き込むため、他のスレッドのワークメモリから変更した値をメインメモリに書き込む遅延時間の間に、別のスレッドはメインメモリの値をrからeadを行うと、このスレッドはメインメモリで最近更新された値を取得できません.
    モノトーンインプリメンテーションでは、1つのスレッドにインスタンスが作成され、そのスレッドのワークメモリで値が更新されたが、mainmemoryに更新される前に別のスレッドがmainmemoryから値を読み出すと、インスタンス参照変数がNULLとして読み込まれ、2つ以上のインスタンスが生成される可能性がある.
    したがって、volatileキーワード付き参照変数を使用して、これらのプライマリ・ストレージとの同期動作を保証し、各スレッドがプライマリ・ストレージから直接読み取りおよび書き込みできるように、単一の色調を実現する必要があります.

    Bill Pugh Singleton


    以上の3つの単色調実現方式の利点はいずれも体現されている.単純で不活性なロードを実現し、メモリを浪費せず、マルチスレッドで安全に使用し、クラス内部に静的クラス変数を持つクラス構造を特徴とする.静的クラスの作成者はgetInstance()を使用する場合にのみインスタンスを作成します.
    public class BillPughSingleton {
        
        private BillPughSingleton(){}
        
        private static class SingletonHelper{
            private static final BillPughSingleton INSTANCE = new BillPughSingleton();
        }
        
        public static BillPughSingleton getInstance(){
            return SingletonHelper.INSTANCE;
        }
    }

    Enum Singleton


    先に紹介したBill Pugh SingletonなどのSingleton実装手法では,JavaのReflection APIで提供されるset Access()がクラスのプライベートジェネレータを呼び出すことができるため,Enumを用いた実装手法が出現している.
    
    import java.lang.reflect.Constructor;
    
    public class PrivateInvoker{
       public static void main(String[] args) throws Exception{
            Constructor<?> con = Private.class.getDeclaredConstructors()[0];
           	//private한 생성자에 접근이 가능한 문제점 발생
            con.setAccessible(true);
            Private p = (Private) con.newInstance();
        }
    }
    
    class Private{
       private Private(){
           System.out.println("Hello!");
        }
    }
    Enumは定数のみを含むクラスで、クラスのようにすべてのメソッドとジェネレータを持つことができますが、固定定数の集合であるため、コンパイル時にすべての値を知る必要があるため、ジェネレータはprivateに制限されます.したがって、インスタンスを生成できないモノトーンの性質があります.Enum Singletonの実施と利点は以下のとおりである.
    public enum EnumSingleton {
    	INSTANCE;
    }

  • 実現が簡単である.

  • thread safe

  • Reflection X
    set accessable()は適用されないため、ジェネレータにアクセスできず、重複生成の問題を解決します.

  • JVMはEnumのシリアル化を保証する
    Enum以外のモノトーンオブジェクトでは、ファイルに格納されている場合や他のサーバに送信された場合に使用されるシリアル化(シリアル化)を使用すると、クラスの作成者がプライベートであっても新しいインスタンスが生成されます.
  • 参考資料


    デザインモード-モノトーン
    マルチモノトーン
    Enum Singleton
    シリアルおよび逆シリアル