JAva設計モード——単例モードの簡単な実現と理解

10085 ワード

単例モードとは?Singleton Pattern(Singleton Pattern)はJavaの中で最も簡単な設計モードの一つです.このタイプの設計モードは作成型モードに属し、オブジェクトの作成に最適な方法を提供します.
このモードは、独自のオブジェクトを作成し、単一のオブジェクトのみが作成されることを保証する単一のクラスに関連します.このクラスは、クラスのオブジェクトをインスタンス化する必要がなく、直接アクセスできる独自のオブジェクトにアクセスする方法を提供します.
単例モードの利点は?1、メモリには1つのインスタンスしかなく、メモリのオーバーヘッドを削減し、特に頻繁にインスタンスを作成し、破棄します.2、資源の多重占有を避ける.
キー:コンストラクタはプライベート
注:1、単一のインスタンスクラスには1つのインスタンスしかありません.2、単一のインスタンスクラスは、独自の一意のインスタンスを作成する必要があります.3、単一のクラスは、他のすべてのオブジェクトにこのインスタンスを提供する必要があります.
一般的ないくつかの単一モード:
単例モードにはいろいろな書き方がありますが、ほとんどの書き方は多かれ少なかれ不足しています.これらの書き方をそれぞれご紹介します.餓漢式##
public class SingleObject {
    //   SingleObject      
    private static SingleObject instance = new SingleObject();
    //       private,           
    private SingleObject(){}
    //         
    public static SingleObject getInstance(){
        return instance;
    }
}

上記のコードから、クラスのコンストラクタはprivateで修飾され、他のクラスがクラスをインスタンス化できないことを保証し、クラス自身が静的インスタンスを作成し、getInstance()メソッドでインスタンスを返します.
餓漢式単例モードはクラスロード時にこのクラスのインスタンスオブジェクトを作成し、インスタンスオブジェクトはプログラムの全実行サイクルにわたって存在し、複数のスレッドが複数のインスタンスを作成することはなく、マルチスレッドの同期問題を回避する.
しかし、クラスの欠点も明らかであり、遅延ロードがないため、クラスのインスタンスが使用されない前にロードプロセスでインスタンスが作成されており、インスタンスが使用されなくても作成され、メモリ領域が浪費される.
##怠け者式—スレッドが安全ではありません##
public class SingleObject2 {
    //               
    private static SingleObject2 instance;  
    //       private,           
    private SingleObject2(){}   
    //         
    public static SingleObject2 getInstance(){  
        //          ,           
        if (instance == null) {  
            instance = new SingleObject2();  
      }  
      return instance; 
    }
}

上記のコードから分かるように、同様にprivateで修飾されたコンストラクタは、他のクラスにインスタンス化されないようにするが、このクラスは自分のインスタンスオブジェクトを静的なプライベート属性とし、newインスタンス化を行わず、getInstance()メソッドでinstanceインスタンスオブジェクトが空かどうかを判断します.単一のインスタンスが作成されている場合、取得インタフェースを再呼び出しても新しいオブジェクトは再作成されず、以前に作成したオブジェクトに直接戻ります.
単一インスタンスの使用回数が少なく、単一インスタンスの作成に消費されるリソースが多い場合は、単一インスタンスのオンデマンド作成を実現する必要があります.この場合、この方法を使用します.ただし、上記のコードはスレッドセキュリティの問題を考慮していません.複数のスレッドでgetInstance()メソッドが同時に呼び出され、複数のインスタンスが作成される可能性があります.そのため、synchronizeをロックしてスレッド同期の問題を解決する必要があります.
##怠け者式—スレッドセキュリティ##
public class SingleObject3 {

    //               
    private static SingleObject3 instance;  
    //       private,           
    private SingleObject3(){}   
    //              synchronize   ,    
    public static synchronized SingleObject3 getInstance(){ 
        //          ,           
        if(instance == null){     
            return instance = new SingleObject3();     
        }else{     
            return instance;     
        }     
    }
}

以上のコードは,怠け者式に基づいてgetInstance()メソッドにsynchronizeロックフラグを用いてスレッドの安全を保証しただけであるが,synchronizeで修飾したメソッドの実行速度は通常のメソッドよりはるかに遅く,複数回呼び出すとプログラムの実行速度が大幅に低下する.
##ダブルチェック/ダブルロック##
public class SingleObject4 {
    //               
    private static SingleObject4 instance;  
    //       private,           
    private SingleObject4(){}   
    //         
    public static  SingleObject4 getInstance(){ 

        //          ,    
        if(instance == null){     
            synchronized(SingleObject4.class){
                if (instance == null ) {
                    instance = new SingleObject4();
                }
            }
        }
        return instance;
    }
}

上記の同期コードブロックの外に1層のinstanceが空であるという判断が見られる.シングル・インスタンス・オブジェクトは一度だけ作成する必要があるため、getInstance()を後で再度呼び出す場合は、シングル・インスタンス・オブジェクトを直接返す必要があります.したがって、ほとんどの場合、getInstance()を調整しても同期コードブロックに実行されず、プログラムのパフォーマンスが向上します.ただし、2つのスレッドA,Bがif(instance==null)文を実行すると、単一のオブジェクトが作成されていないとみなされ、スレッドがBに切断されても同じ文が実行され、Bも単一のオブジェクトが作成されていないとみなされ、2つのスレッドが同期コードブロックを順次実行し、それぞれ1つの単一のオブジェクトが作成される場合も考えられる.この問題を解決するためには、同期コードブロックにif(instance==null)文、すなわち、上述した第2層判定のコードを追加する必要がある.
  • 二重チェックロックは、遅延ロードを実現し、スレッドの同時問題を解決し、実行効率の問題も解決します.

  • しかしながら、ここでは、Java命令における再配置最適化とは、元の意味を変更することなく、命令の実行順序を調整することによってプログラムがより速く実行されることを意味する.JVMにはコンパイラ最適化に関する内容は規定されておらず,つまりJVMは命令再ソートの最適化を自由に行うことができる.
    命令再配置最適化が存在するため、SingletonObject 4を初期化し、instanceフィールドにオブジェクトアドレスを割り当てる順序は不確定である.スレッドが単一のオブジェクトを作成すると、コンストラクションメソッドが呼び出される前に、オブジェクトにメモリ領域が割り当てられ、オブジェクトのフィールドがデフォルト値に設定されます.割り当てられたメモリアドレスをinstanceフィールドに割り当てることができますが、オブジェクトはまだ初期化されていない可能性があります.別のスレッドに続いてgetInstance()を呼び出すと、ステータスが正しくないオブジェクトが取り出され、プログラムがエラーになります.
    でもJDK 1では5以降はvolatileキーワードが追加されました.volatileの1つの意味は、命令の再ソート最適化を禁止することであり、instance変数が付与されたときにオブジェクトが初期化されていることを保証し、上記の問題を回避することである.
    次はvolatileキーワードを追加したコードです.
    public class SingleObject4 {
        //               
        private static volatile  SingleObject4 instance;    
        //       private,           
        private SingleObject4(){}   
        //         
        public static  SingleObject4 getInstance(){ 
    
            //          ,    
            if(instance == null){     
                synchronized(SingleObject4.class){
                    if (instance == null ) {
                        instance = new SingleObject4();
                    }
                }
            }
            return instance;
        }
    }

    ##登録式/静的内部クラス##
    public class SingleObject5 {
        //                 
        private static class SingletonHolder {  
            private static final SingleObject5 INSTANCE = new SingleObject5();  
        }
        //     
        private SingleObject5 (){}  
        //        
        public static final SingleObject5 getInstance() {  
            return SingletonHolder.INSTANCE;  
        } 
    }

    この方法は、クラス・ロード・メカニズムを使用して、instanceインスタンスが1つしか作成されないことを保証します.餓漢式と同様にクラスロードメカニズムも利用されており,インスタンス化オブジェクトは実際に静的内部クラスで行われるため,静的内部クラスを呼び出さない限りそのオブジェクトはインスタンス化されないため,マルチスレッド同時化の問題はない.
    ##列挙##
    public enum SingleObject6 {
         INSTANCE;  
        public void whateverMethod(){}
    }
    

    见ているのはとても简単で、But、私はまだ理解していません~私のこの白を许してください~~1つの大神のブログの上でこのように书いています:
    上記のいくつかのインプリメンテーションの一例には共通の欠点があります.1)シーケンス化を実現するために追加の作業が必要です.そうしないと、シーケンス化されたオブジェクトを逆シーケンス化するたびに新しいインスタンスが作成されます.2)反射を使用してプライベートコンストラクタを強制的に呼び出すことができる(これを回避するには、コンストラクタを変更して、2番目のインスタンスを作成するときに例外を放出させることができます).列挙クラスは、スレッドのセキュリティと反射呼び出し防止コンストラクタのほかに、逆シーケンス化のときに新しいオブジェクトを作成することを防止する自動シーケンス化メカニズムを提供します.したがって、Effective Javaでは著者が推奨する方法.しかし、実際の仕事では、そう書く人はめったに見られません.
    大神まとめ:
    二重チェックロックと静的内部クラスの方式は大部分の問題を解決することができ、普段の仕事で最も多く使用されているのもこの2つの方式である.列挙の仕方は完璧にいろいろな問題を解決していますが、この書き方は多少疎遠な感じがします.
    以上、菜鳥教程とネット上の各ブログの自分のコードを参考にして手書きしました~