JAva面接の単例モデルの7つの実現方式

8095 ワード

多くのjavaプログラマー、Androidプログラマーが面接の途中で、きっと単例モードの質問をされたことがあると信じています.
単例モードが頻繁に面接問題とされているのは間違いなく、かなり重要であるに違いない.できないわけにはいかない.ここでは、各単例方式の実現方法と長所と短所を系統的に紹介する.
単例モードが実現する前に、まず単例モードとは何かを見てみましょう.単例モードを使用する必要がある場合は何ですか?私たちは一人ずつ来ます.
1.単例モードとは何ですか.
一般的な説明:濃縮すると、作成モードに属し、グローバルにはインスタンスオブジェクトが1つしか作成されず、頻繁に使用されます.
私たちは一つの概念を理解する前に、最も良い方法はまずその特徴を理解することです.例えば、あなたは生まれてから今まで犬を舐めるのを見たことがありません.では、脊索動物門、脊椎動物亜門、哺乳綱、真獣亜綱、肉食目、裂脚亜目、犬科動物を紹介します.中国語では「犬」とも呼ばれ、犬は世界各地に分布している.犬を舐めるって何か知ってる?言ったことは言わなかったに違いないが、犬を舐めるのは、四本の足、2本の耳、2本の目、全身に毛が生えていて、しかもワンワン鳴いていて、怒っても人を噛むことができて、人に会って噛むか、舐めるか、それではあなたはきっと大体の様子があるだろう.
   では、単例モードに戻ります.少し遠くまで引っ張って、カード単例モードの特徴を見てみましょう.
  • 1、単一のクラスには1つのインスタンスしかありません.
  • 2、単一のインスタンスクラスは、独自の一意のインスタンスを独自に作成する必要があります.
  • 3、単一のクラスは、このインスタンスを他のすべてのオブジェクトに提供する必要があります.
  • 4、主に1つのクラスが頻繁に作成され、廃棄され、資源の浪費を招く問題を解決する.

  • まずコード栗を挙げます:コードは簡単に見ればいいですが、以下は具体的に言います.
    public class InstanceTest {
    
    	private static InstanceTest instance = null;
    
    	//        
    	private InstanceTest() {
    
    	}
    
    	public static InstanceTest getInstance() {
    		if (instance == null) {
    			instance = new InstanceTest();
    		}
    		return instance;
    	}
    
    }

    2.どのような場合に単例モードを使用する必要がありますか?
             単例モードの特徴を見終わって、これは実は理解しにくくなくて、単例モードは結局資源の浪費の問題を解決するためで、1つのクラスの中の方法で、変数などは頻繁に使用されて、システムを使い終わってまた回収しなければならなくて、もし単例がなければ、1回で1つを作成して、あまりにも頻繁に使用して、きっと1山の実例の対象を作成することを招いて、私達はすべて知っていて、インスタンスオブジェクトを作成するにはメモリがかかります.リソースがかかりますが、システムリソースが限られています.無駄にしないのはもちろん無駄ではありません.だから、単例モードが来ました.もし一つのクラスが魅力的であれば、それをずっと一つにして、それを単例モードにしてください.何回使っても、私は1つで、全天候にいます.
    3.単例モードの実現方式
    (1)餓漢式
    
    public class InstanceTest {
    
    	private static InstanceTest instance = new InstanceTest();
    
    	//        
    	private InstanceTest() {
    
    	}
         //       ,       InstanceTest      ,         。
    	public static InstanceTest getInstance() {
    		
    		return instance; //          ,          
    	}
    
    }
    

    说明:名前を闻くと、このような単例の実现方法には、せっかちで、お腹が空いたらせっかちで、せっかちで、クラスが作成されるとインスタンスオブジェクトが作成されます.あなたが使うかどうかにかかわらず、私は先に作成してから、喉が渇いて耐えられませんか?
      利点:外部アクセスメソッドは、パフォーマンスが高く、作成したインスタンスオブジェクトを直接返す必要がなく、スレッドが安全であることから、2つ目の実装方法を見るとわかります.
       欠点:クラスのロードの时に初期化して、急いだ结果は浪费で、私はまだ要っていないで、あなたはよくして、もし私が要っていないならば、浪费しました;
    (2)怠け者式
    public class InstanceTest {
    
    	private static InstanceTest instance = null;//     ,       
    
    	//        
    	private InstanceTest() {
    
    	}
         //       ,       InstanceTest      ,         。
    	public static InstanceTest getInstance() {
    		if(instance==null) { //     ,           ,    ,     
    			instance = new InstanceTest();
    		}
    		return instance; //          ,          
    	}
    }
    
    

     説明:怠け者式は、表現するのがおっくうで、使わないときは、作成することは不可能で、一生自発的に作成することはできません.使うときは、前に作成したかどうか、していないかを見てから作成して、あなたに作成することはできません.これが真の意味を失うことだ.
    利点:リソースの利用率を高め、使用するときに再作成し、リソースを無駄にしない.
    欠点:非スレッドが安全である.つまり、マルチスレッドが高い場合、instance=new InstanceTest()である可能性がある.この方法は同時にいくつかのスレッドによって同時に呼び出され,複数のオブジェクトが現れるため,単一の例の意味が失われる.
    注意:怠け者の単例モードは、非スレッドで安全ですが、実際の開発では、このような単例を作成する方法が一般的に見られるかもしれません.つまり、餓漢式単例モードは依然として広く使用されています.原因は簡単です.私たちは実際の開発では、多スレッドが高く合併するリスクがなく、資源利用率の問題も考慮されています.自然怠け者式は多くの場合の第一選択となっている.
    (3)怠け者+Synchronized
    public class InstanceTest {
    
    	private static InstanceTest instance = null;//     ,       
    
    	//        
    	private InstanceTest() {
    
    	}
         //       ,       InstanceTest      ,         。
    	// synchronized  getInstance()       
    	public static synchronized InstanceTest getInstance() {
    		if(instance==null) { //     ,           ,    ,     
    			instance = new InstanceTest();
    		}
    		return instance; //          ,          
    	}
    }

    説明:この方法と怠け者の違いはキーワードを追加することです.synchronizedというキーワードの役割は明らかに怠け者のスレッドが安全ではない問題を解決するためです.synchronizedはgetInstance()にスレッドがアクセスしているときに、getInstance()メソッドにアクセスしたい他のスレッドに伝えます.今は誰かが穴をあけています.何をすればいいですか.それが使い終わったら、あなたたちの番になります.これが防止され、instance=new InstanceTest();複数のスレッドによって呼び出される問題.
    利点:怠け者式の利点とスレッドセキュリティ
    欠点:同時アクセスの時、性能は比較的に低くて、原因はとても簡単で、同期ロックを加えて、待つことを意味して、並んで、性能は自然に比較的に低いです;
    (4)怠け者式+ダブルチェックロック法
    public class InstanceTest {
    
    	private static InstanceTest instance = null;//     ,       
    
    	//        
    	private InstanceTest() {
    
    	}
         //       ,       InstanceTest      ,         。
    	// synchronized  getInstance()       
    	public static  InstanceTest getInstance() {
    		if(instance==null) {//     ,           ,    ,     
    			//               ,    synchronized ,      ,      
    			//         instance null  ,  ,          ,
    			//   1
                   synchronized (InstanceTest.class) {			
    				if(instance==null) { //         ,      //   2
                      
    					instance = new InstanceTest(); //   3
    				}
    			}
    		}
    	
    		return instance; //          ,          
    	}
    }

    説明:この方法も怠け者式に基づいて改善され、synchronizedをインスタンスを作成する方法に追加しましたinstance=new InstanceTest();
    注釈のように二重検査を加えた.
    利点:3つ目の方法よりもパフォーマンスが優れ、怠け者の利点が継承されています.
    欠点:通常スレッドは安全で、低確率は安全ではありません
    この原因を具体的に説明します.まず、インスタンス化対象プロセスで何が起こったかを見てみましょう.
    (1)オブジェクトにメモリ領域を割り当てる
    (2)初期化デフォルト
    (3)構築方法の実行
    (4)割り当てたメモリ領域にオブジェクトを向ける
    そして上のコードをよく見ると、私は3つの位置、位置1、位置2、位置3をマークしました.もしスレッド1が位置2に実行されたら、スレッド2はちょうど位置1に実行されました.この時、スレッド1は位置2のブロックのコードをロックしました.スレッド2は位置1で待っています.スレッド1は位置2で判断し、順調に位置3に入りましたが、 Javaプラットフォームメモリモデルは、性能などの理由でメモリモデルがいわゆる「無秩序書き込み」を許可するため、第(3)歩と第(4)歩が同調する可能性があります.つまり、まず相手をあげます.私は後で構造方法を実行します.実際にはnullをあげます.この原理は実際の生活でもよく使われています.例えば、あなたの友达が大宝剣に行きますが、お金を持っていません.あなたにお金を借りて、あなたは先に行って、私はあなたの微信にお金を送ることができます.実は、あなたの友达が大宝剣にいるとき、彼はお金を払っていません.しかし、彼の大宝剣が終わったら、実際に払うことができます.この时、あなたはきっと彼にお金を送ったに違いありません.この理屈だから,話が雑だ.3ステップ目と4ステップ目が位置を変えた場合、スレッド1はインスタンスオブジェクトを初期化するときに同期ロックが開かれ、スレッド2がロックコードブロックに入り、位置2に実行されるとinstanceが空でないと判断し、スレッド2は偽のインスタンスペアを返し、コンストラクション関数を実行していないインスタンスなど、スレッド1がインスタンス化方法を実行したときに、スレッド1は、コンストラクタメソッドを実行したインスタンスを正常に返す、すなわち、スレッド1は正常に返され、スレッド2は偽のインスタンスを返す可能性があり、初期化が完了していない.
    (5)怠け者式+ダブルチェックロック+volatile
    
    public class InstanceTest {
    
    	private static volatile  InstanceTest instance = null;//     ,       
    	//     volatile   
    
    	//        
    	private InstanceTest() {
    
    	}
         //       ,       InstanceTest      ,         。
    	// synchronized  getInstance()       
    	public static  InstanceTest getInstance() {
    		if(instance==null) {//     ,           ,    ,     
    			//               ,    synchronized ,      ,      
    			//         instance null  ,  ,          ,
    			synchronized (InstanceTest.class) {			
    				if(instance==null) { //         ,    
    					instance = new InstanceTest();
    				}
    			}
    		}
    	
    		return instance; //          ,          
    	}
    }

    説明:この方法は4つ目の方法とほぼ同じで、唯一異なるのはインスタンスオブジェクトが宣言するときにvolatileキーワードを追加することです.このキーワードの役割も明らかに4つ目の方法の不足を補うためです.   だからここまで私达は难しく発见して、第3种、第4种、第5种の方式は実は第2种の方式のアップグレード版で、理论的には同じ方式で、ただ3、4、5种の方式はすべてパッチを打って、そこで问题が発生してどこを补って、3回补いました;
    利点:スレッドが安全で、性能が最適化される.
    短所:個人的に書くのがちょっと面倒だと思います.
    (6)静的クラス内部ロード
    public class InstanceTest {
    
    	private InstanceTest() {
    
    	}
    
    //     static  ,   InstanceTest      ,     
    	private static class InstanceTestHolder {
    		private static InstanceTest instance = new InstanceTest();
    	}
        
    	public static InstanceTest getInstance() {
    		return InstanceTestHolder.instance;
    	}
    }

    説明:この方式では、コードは複雑ではなく、単例のInstanceTestクラスの構造方法が私有化され、クラスの中に新しい内部クラスが作成され、InstanceTestHolderはstatic静的修飾子によって修飾された.静的内部クラスは、単一の例のロード時にロードされるのではなく、getInstance()メソッドを呼び出すときにロードされ、怠け者モードのような効果を達成します.この方法はスレッドが安全です.
    利点:パフォーマンスが高く、リソースの利用率が高く、スレッドが安全である.
    短所:後ろを見続ける
    (7)列挙方法
    
    public enum InstanceEm {
    	INSTANCE;
    
    	//     ,      ,add()  
    	public void add() {
    		//           
    	}
    }
    
    /**
     *       
     * @author xyc
     *
     */
    public class TestInstance  {
    	public static void main(String[] args) {
          InstanceEm.INSTANCE.add();//   add()  
    	}
    }
    

    説明:第1セグメントコードは列挙クラスを作成し、第2セグメントコードは使用します.この書き方は、『Effective Java』という本の著者Josh Blochが提唱した方法です.
    男の人の書き方はとてもふわふわしていて、とても簡単で、男の人を装って使うことを推薦して、しかし、完璧なものは存在しないで、このような書き方の唯一の欠点は継承することができなくて、列挙類は他の類に継承することができなくて、その他はすべて悪くありません;持って行って追い詰めることができます;
    オ、もう一つ問題があります.前の6つの方法はすべて構造方法を私有化しましたが、絶対的な外部クラスがアクセスできないわけではありません.java反射を使って私有コンストラクタを強制的に呼び出すことができます.第7の方法は、この問題を解決しました.
    利点:コードがふわふわし、スレッドが安全で、単例の目的を達成する
    短所:継承できない