なぜ列挙クラスを用いて単例モードを実現するのがますます流行しているのか.


前言
単例モードはJava設計モードの中で最も簡単なもので、1つのクラスだけで単例モードを実現することができますが、単例モードを軽視することはできません.設計から言えば簡単ですが、実現の中で非常に多くの穴に遭遇するので、安全ベルトを締めて、車に乗ります.
単一モードの定義
シングル・インスタンス・モードとは、プログラムの実行中に一度だけインスタンス化され、Javaの静的変数に似たグローバル・ユニークなオブジェクトを作成することですが、シングル・インスタンス・モードは静的変数よりも優れており、静的変数はプログラムの起動時にJVMがロードされ、使用しないと大量のリソースの浪費をもたらし、シングル・インスタンス・モードは怠け者のロードを実現することができます.インスタンスを使用するときにインスタンスを作成できます.開発ツールクラスライブラリの多くのツールクラスには、スケールスレッドプール、キャッシュ、ログオブジェクトなど、1つのオブジェクトを作成するだけで、複数のインスタンスを作成すると、リソースの浪費、結果の処理の不一致などの予知できない問題をもたらす可能性があります.
単例の実現構想
  • 静的インスタンスオブジェクト
  • 私有化構築方法、構築方法によるインスタンスの作成を禁止する
  • は、一意のインスタンス
  • を返すための共通の静的方法を提供する.
    単一例のメリット
  • オブジェクトは1つのみで、メモリのコストが少なく、パフォーマンスが良い
  • リソースの多重占有を回避する
  • システムにグローバルアクセスポイントを設定し、リソースアクセスの最適化と共有
  • 単一モードの実装
    単例モードの書き方は餓漢モード、怠け者モード、二重検査ロックモード、静的内部類単例モード、列挙類実現単例モードの5つの方式があり、その中で怠け者モード、二重検査ロックモードの2つの方式があり、もしあなたの書き方が適切でなければ、マルチスレッドの場合、単例ではないか単例ではないなどの問題が存在し、具体的な原因は、後の対応で説明します.私たちは最も基本的な餓漢モードから私たちの単例の作成の道を始めました.
    餓漢モード
    餓漢モードは単純で乱暴な形式を採用し,静的属性を定義する際にオブジェクトを直接インスタンス化した.コードは次のとおりです.
    //            ,       ,         
    public class SingletonObject1 {
        //              
        private static final SingletonObject1 instance = new SingletonObject1();
    
        //        
        private SingletonObject1(){
            //          
        }
    
        //           
        public static SingletonObject1 getInstance(){
            return instance;
        }
    }
    

    餓漢モードの長所と短所
    メリット
  • staticキーワードを使用することで、この変数を参照する際に、この変数についての書き込み操作が完了することが保証されるため、JVMレベルのスレッドセキュリティ
  • が保証される.
    欠点
  • は怠惰なロードを実現することができず、空間の浪費をもたらし、もし1つのクラスが比較的大きいならば、私たちは初期化の時にこのクラスをロードしましたが、私たちは長い間このクラスを使用していません.これはメモリ空間の浪費を招きました.

  • 怠け者モード
    怠け者モードは一種の怠け者モードであり、プログラム初期化時にはインスタンスは作成されず、インスタンスを使用する場合にのみインスタンスが作成されるので、怠け者モードは餓漢モードによる空間浪費問題を解決するとともに、他の問題も導入されているので、まず次の怠け者モードを見てみましょう.
    public class SingletonObject2 {
        //        ,      
        private static SingletonObject2 instance;
    
        //        
        private SingletonObject2(){
    
        }
    
        public static SingletonObject2 getInstance(){
            //    ,         ,      ,      
            if (instance == null)
                instance = new SingletonObject2();
            return instance;
        }
    }

    上記は怠け者モードの実装ですが、上記のコードはマルチスレッドの場合は安全ではありません.単一のインスタンスモードであることは保証されません.複数のインスタンスが発生する可能性があります.複数のインスタンスが発生する場合は、インスタンスオブジェクトを作成するときに発生します.そこで、インスタンス化されたコードを単独で提案し、なぜ複数のインスタンスが発生したのかを分析します.
         1   if (instance == null)
         2       instance = new SingletonObject2();

    2つのスレッドがいずれも1の位置に入ると仮定すると、リソース保護措置がないため、2つのスレッドが同時に判断できるinstanceは空であり、いずれも2のインスタンス化コードを実行するため、複数のインスタンスが発生する.
    上記の分析では、複数のインスタンスが発生した理由が分かりました.インスタンスを作成するときにリソース保護を行うと、複数のインスタンスの問題を解決できますか?確かに、getInstance()メソッドにsynchronizedキーワードを追加することで、getInstance()メソッドが保護されたリソースとなり、複数のインスタンスの問題を解決することができる.synchronizedのキーワードを追加すると、コードは次のようになります.
    public class SingletonObject3 {
        private static SingletonObject3 instance;
    
        private SingletonObject3(){
    
        }
    
        public synchronized static SingletonObject3 getInstance(){
            /**
             *   class  ,     ,             ,
             *               ,        ,        
             *
             */
    
            if (instance == null)
                instance = new SingletonObject3();
            return instance;
        }
    }

    修正後、複数のインスタンスの問題を解決したが、synchronizedキーワードが追加され、コードにロックが追加されたため、新しい問題が導入され、ロックが追加されるとプログラムがシリアル化され、ロックを奪ったスレッドだけがこのコードブロックを実行することができ、システムのパフォーマンスが大幅に低下する.
    怠け者モードの長所と短所
    メリット
  • は怠惰なロードを実現し、メモリスペース
  • を節約した.
    欠点
  • ロックなしではスレッドが安全ではなく、複数のインスタンス
  • が発生する可能性がある.
  • ロックされた場合、プログラムがシリアル化され、システムに深刻な性能問題が発生する
  • .
    ダブルチェックロックモード
    さらに、怠け者モードでのロックの問題について議論します.getInstance()の方法では、ほとんどの操作が読み取り操作であり、読み取り操作はスレッドが安全であるため、各スレッドにロックを持たなければこの方法を呼び出すことができません.ロックの問題を調整する必要があります.これにより、二重チェックロックモード、以下、二重チェックロックモードの一例実装コードブロックが生成される.
    public class SingletonObject4 {
        private static SingletonObject4 instance;
    
        private SingletonObject4(){
    
        }
    
        public static SingletonObject4 getInstance(){
    
            //      ,      ,       ,      
            if (instance == null)
                synchronized (SingletonObject4.class){
                    //              
                    if (instance == null){
                        instance = new SingletonObject4();
                    }
                }
    
            return instance;
        }
    }

    二重検査ロックモードは非常に良い一例実現モードであり、一例、性能、スレッドセキュリティの問題を解決した.上の二重検査ロックモードは完璧に見えるが、実は問題がある.マルチスレッドの場合、空のポインタの問題が発生する可能性がある.問題の原因はJVMがオブジェクトをインスタンス化する時に最適化と指令再ソート操作を行うことである.コマンドの並べ替えとは何ですか?次の例を見て、命令のソートから簡単に理解してください.
        private SingletonObject4(){
         1   int x = 10;
         2   int y = 30;
         3  Object o = new Object();
                    
        }

    上記のコンストラクション関数SingletonObject4()は、1、2、3の順序で記述されているため、JVMはそれを命令的に並べ替えるので、実行順序は3、1、2、2、3、1である可能性があり、その実行順序にかかわらず、JVMは最後にインスタンス化を完了することを保証します.コンストラクション関数で操作が多い場合、効率を向上させるために、JVMはコンストラクション関数のプロパティがすべてインスタンス化されていない場合にオブジェクトを返します.二重検出ロックに空ポインタの問題が発生した原因はここにあり、あるスレッドがロックを取得してインスタンス化すると、他のスレッドは直接インスタンスを取得して使用し、JVM命令が並べ替えられたため、他のスレッドが取得したオブジェクトは完全なオブジェクトではない可能性があるため、インスタンスを使用すると空ポインタの異常な問題が発生する.
    二重チェックロックモードが空ポインタ異常をもたらす問題を解決するには、volatileキーワードを使用するだけで、volatileキーワードはhappens-before原則に厳格に従い、すなわち、読み取り操作の前に書き込み操作がすべて完了しなければならない.volatileキーワードを追加した後の単一モードコード:
        //   volatile   
        private static volatile SingletonObject5 instance;
    
        private SingletonObject5(){
    
        }
    
        public static SingletonObject5 getInstance(){
    
            if (instance == null)
                synchronized (SingletonObject5.class){
                    if (instance == null){
                        instance = new SingletonObject5();
                    }
                }
    
            return instance;
        }
    }
    volatileキーワードを追加した後の二重チェックロックモードは、マルチスレッドの場合でもスレッドの安全性と性能の問題がないことを保証する比較的良い単例実装モードである.
    静的内部クラス単一モード
    静的内部クラス単例モードは単例所有者モードとも呼ばれ、インスタンスは内部クラスによって作成される.JVMは外部クラスをロードする過程で静的内部クラスをロードしないため、内部クラスの属性/方法が呼び出された時だけロードされ、その静的属性を初期化する.静的属性はstaticによって修飾され、一度だけインスタンス化されることを保証し、インスタンス化順序を厳格に保証する.静的内部クラスの単一モードコードは次のとおりです.
    public class SingletonObject6 {
    
    
        private SingletonObject6(){
    
        }
        //      
        private static class InstanceHolder{
            private  final static SingletonObject6 instance = new SingletonObject6();
    
        }
        
        // 
        public static SingletonObject6 getInstance(){
            //        
            return InstanceHolder.instance;
        }
    }

    静的内部クラスの単一モードは優れた単一モードであり、オープンソースプロジェクトで比較的よく使われる単一モードである.ロックがかかっていない場合、マルチスレッドでのセキュリティが保証され、パフォーマンスの影響やスペースの浪費がありません.
    列挙クラス実装単例モード
    列挙クラス実装単例モードはeffective java著者らが極力推奨する単例実装モードである.列挙タイプはスレッドが安全であり、一度しかロードされないため、設計者は列挙のこの特性を十分に利用して単例モードを実現し、列挙の書き方は非常に簡単である.さらに、列挙タイプは、使用される単一のインプリメンテーションの中で破壊されない唯一の単一のインプリメンテーションモードである.
    public class SingletonObject7 {
    
    
        private SingletonObject7(){
    
        }
    
        /**
         *           ,        
         */
        private enum Singleton{
            INSTANCE;
    
            private final SingletonObject7 instance;
    
            Singleton(){
                instance = new SingletonObject7();
            }
    
            private SingletonObject7 getInstance(){
                return instance;
            }
        }
    
        public static SingletonObject7 getInstance(){
    
            return Singleton.INSTANCE.getInstance();
        }
    }

    単例モードを破壊する方法と解決方法
    1.列挙方式を除き、他の方法はいずれも反射によって単一例を破壊する.反射は構造方法を呼び出して新しいオブジェクトを生成するので、単一例の破壊を阻止したい場合は、構造方法で判断することができ、既存のインスタンスがあれば、新しいインスタンスの生成を阻止する.解決方法は以下の通りである.
    private SingletonObject1(){
        if (instance !=null){
            throw new RuntimeException("      ,    getInstance()    ");
        }
    }

    2、単一クラスがシーケンス化インタフェースSerializableを実現した場合、逆シーケンス化によって単一例を破壊することができるので、シーケンス化インタフェースを実現しなくてもよいし、シーケンス化インタフェースを実現しなければならない場合、逆シーケンス化方法readResolve()を書き換えることができ、逆シーケンス化時に直接関連する単一オブジェクトに戻ることができる.
      public Object readResolve() throws ObjectStreamException {
            return instance;
        }

    最後に
    小さな広告を打って、コードをスキャンして微信の公衆番号に注目することを歓迎します:“平頭兄の技術の博文”、一緒に進歩しましょう.