Java版の7種類のシングルケースの書き方例


前言
今日はある文章を見ました。 一例のDCLの前にプラスします Vです。この言葉だけは私に任せてください。 一例モードでもう一度詳しく見ました。
Javaの中の シングルモデルは私たちがいつも使っているデザインの一つです。みんなよく知っています。だから、この文章は自分で覚えただけです。
シングルケースモードはJavaの中で最も簡単な設計モードの一つである。このタイプの設計モードは作成モードに属し、オブジェクトを作成するための最良の方法を提供する。
単一の例のパターンは、単一のオブジェクトのみが作成されることを確認しながら、自分のオブジェクトを作成する責任があるクラスに関連します。このクラスは、その唯一のオブジェクトにアクセスする方法を提供しています。直接にアクセスできます。このクラスのオブジェクトを実装する必要はありません。
  • 単例クラスは一例しかありません。
  • 単例類は自分で自分の唯一のインスタンスを作成しなければなりません。
  • 単例クラスは、他のすべてのオブジェクトにこの例を提供しなければならない。
  • Java版の7種類のシングルケースの書き方
    一:怠け者、スレッドが安全ではない。
    この表記はlazy loadingがはっきりしていますが、致命的なのはマルチスレッドでは正常に動作しません。
    
    public class Singleton{
     private static Singleton instance;
     private Singleton(){};
     public static Singleton getInstance(){
     if (instance == null) {
      instance = new Singleton();
     }
     return instance;
     }
    }
    二:怠け者、スレッドの安全
    このような書き方はマルチスレッドの中でよく働くことができ、また良好なlazy loadingを備えているように見えますが、残念なことに、効率が低く、99%の場合は同期が必要ではありません。
    
    public class Singleton{
     private static Singleton instance;
     private Singleton(){};
     public static synchronized Singleton getInstance(){
     if (instance == null) {
      instance = new Singleton();
     }
     return instance;
     }
    }
    三:餓漢
    このような方式は、クラスローダー機構に基づいて、マルチスレッドの同期問題を回避していますが、instanceはクラスローディング時に実装されています。クラスローディングの原因は様々ですが、一例モードでは、get Instanceメソッドを呼び出すことが多いです。他の方式(または他の静的方法)によるクラスローディングも特定できません。この時初期化instanceは明らかにlazy loadingの効果を達成していません。
    
    public class Singleton{
     private static Singleton instance = new Singleton();
     private Singleton(){};
     public static Singleton getInstance(){
      return instance;
     }
    }
    腹がへって、種が変わる。
    表面上の違いが大きいように見えますが、第三の方式はほぼ同じです。クラス初期化という実用化のinstanceです。
    
    public class Singleton{
     private static Singleton instance = null;
     private Singleton(){};
     static {
      instance = new Singleton();
     }
     public static Singleton getInstance(){
      return instance;
     }
    }
    五:静的内部類
    この方式は同じくclassloderの機構を利用してinstanceを初期化する時に1つのスレッドしかないことを保証しています。3つ目と4つ目の方式とは違っています。3つ目と4つ目の方式はSingleton類が搭載されたらinstanceが実用化されます。この方式はSingleton類が搭載されています。instanceが初期化されるとは限らない。Singleton Holder類はアクティブに使用されていないので、getInstanceメソッドを呼び出すと、Singleton Holder類を搭載していることが表示され、instanceを実装します。想像してみてください。もし実際にinstanceが資源を消耗しているなら、彼にロードを遅延させたいです。もう一方、Singleton類のロード時に実例化したくないです。Singleton類が他のところで積極的に使用されてローディングされるかもしれないということを確保できないので、この時に実用化されたinstanceは明らかに不適切です。この時、この方式は第三と第四の方式より合理的です。
    
    public class Singleton{
     private static class SingletonHolder{
      private static final Singleton INSTANCE = new Singleton();
     }
     private Singleton(){};
     public static Singleton getInstance(){
      return SingletonHolder.INSTANCE;
     }
    }
    静的内部類は最も完璧な方法のように見えますが、実はそうではないです。反射攻撃やアンチプログレッシブ攻撃もあります。下記のコードを見てください。
    
    public static void main(String[] args) throws Exception {
     Singleton singleton = Singleton.getInstance();
     Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
     constructor.setAccessible(true);
     Singleton newSingleton = constructor.newInstance();
     System.out.println(singleton == newSingleton);
    }
    六:列挙
    このような方式はEffective Javaの著者Josh Blochが提唱する方式で、最適な一例実現モードはエニュメレーションモードです。エニュメレーションの特性を利用して、JVMにスレッドの安全と単一のインスタンスの問題を保証してもらえます。また、アンチプログレッシブが新たなオブジェクトを作成することを防止できます。このほかに、書き方はとても簡単です。
    
    public enum Singleton {
     INSTANCE;
     public void get() {
      System.out.println("");
     }
    }
    逆コンパイルで見ましたが、列挙は staticブロックで行われるオブジェクトの作成。
    
    public final class com.loadclass.test.Singleton extends java.lang.Enum<com.loadclass.test.Singleton> {
     public static final com.loadclass.test.Singleton INSTANCE;
    
     public static com.loadclass.test.Singleton[] values();
     Code:
      0: getstatic  #1     // Field $VALUES:[Lcom/loadclass/test/Singleton;
      3: invokevirtual #2     // Method "[Lcom/loadclass/test/Singleton;".clone:()Ljava/lang/Object;
      6: checkcast  #3     // class "[Lcom/loadclass/test/Singleton;"
      9: areturn
    
     public static com.loadclass.test.Singleton valueOf(java.lang.String);
     Code:
      0: ldc   #4     // class com/loadclass/test/Singleton
      2: aload_0
      3: invokestatic #5     // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
      6: checkcast  #4     // class com/loadclass/test/Singleton
      9: areturn
    
     public void get();
     Code:
      0: getstatic  #7     // Field java/lang/System.out:Ljava/io/PrintStream;
      3: ldc   #8     // String
      5: invokevirtual #9     // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      8: return
    
     static {};
     Code:
      0: new   #4     // class com/loadclass/test/Singleton
      3: dup
      4: ldc   #10     // String INSTANCE
      6: iconst_0
      7: invokespecial #11     // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic  #12     // Field INSTANCE:Lcom/loadclass/test/Singleton;
      13: iconst_1
      14: anewarray  #4     // class com/loadclass/test/Singleton
      17: dup
      18: iconst_0
      19: getstatic  #12     // Field INSTANCE:Lcom/loadclass/test/Singleton;
      22: aastore
      23: putstatic  #1     // Field $VALUES:[Lcom/loadclass/test/Singleton;
      26: return
    }
    七:ダブルチェックロック(DCL:doub-checed locking)
    
    public class Singleton {
     // jdk1.6   ,     private volatile static SingleTon instance     DCL    。
     // volatile  instance          ,           ,      。
     // volatile      java              ,         。
     private volatile static Singleton singleton;
     private Singleton(){};
     public static Singleton getSingleton() {
      if (singleton == null) {
       synchronized (Singleton.class) {
        if (singleton == null) {
         singleton = new Singleton();
        }
       }
      }
      return singleton;
     }
    }
    DCL及び解決方法&説明:
    遅延ローディング法の同期実装によって生じる性能の低い問題に対しては、DCL、すなわちダブルチェックの加錠(Double Check Lock)の方法を採用して、get Instance()メソッドが起動されるたびに同期することを回避することができる。
    Double-Checked Lockingはとても完璧に見えます。残念ながら、Javaの言語規範によって上のコードは信頼できません。
    上記の問題が発生し、最も重要な2つの原因は以下の通りである。
  • コンパイラはプログラム命令を最適化して、cpu処理速度を速めます。
  • マルチコアcpuダイナミック調整コマンドの順序は、並列演算能力を加速させます。
  • 問題が発生する順番
  • スレッドAは、オブジェクトが実装されていないことを発見し、実用化を開始しようとする
  • コンパイラがプログラム命令を最適化したため、オブジェクトはコンストラクションが起動されない前に、共有変数の参照を部分構造のオブジェクトに向けて指し示し、オブジェクトは完全に実装されていないが、すでにnullではない。
  • スレッドBは、部分的に構成されているオブジェクトがnullではないことが分かりました。このオブジェクトに直接戻りました。
  • 解決策:
    instanceをvolatile、つまりprvate volatile static Singleton instanceとして宣言することができます。
    オンラインパスBでvolatile変数を読むと、スレッドAはこのvolatile変数を書く前に、すべての可視共有変数の値が直ちにスレッドBに表示されるようになります。
    まとめ:
    単一の例が異なるクラスローダーによってローディングされている場合、複数の単一のインスタンスが存在する可能性がある。遠端アクセスではないと仮定して、例えばいくつかのservlet容器は各servletに対して全く異なる種類のローダーを使用しています。このようにすれば、2つのservletが1つの単例クラスにアクセスすると、それぞれのインスタンスがあります。
    
    private static Class getClass(String classname) throws ClassNotFoundException {
     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     if (classLoader == null) {
      classLoader = Singleton.class.getClassLoader();
     }
     return (classLoader.loadClass(classname));
    }
    Singletonがjava.io.Serializableインターフェースを実現すれば、このクラスのインスタンスは、プログレッシブされ、復元されるかもしれない。いずれにしても、単一の例のオブジェクトを順番に並べて、次に複数のオブジェクトを復元すると、複数の単例クラスのインスタンスがあります。
    
    public class Singleton implements Serializable {
     public static Singleton INSTANCE = new Singleton();
     private Singleton(){}
     //ObjectInputStream.readObject  
     private Object readResolve() {
      return INSTANCE;
     }
    }
    締め括りをつける
    以上はこの文章の全部の内容です。本文の内容は皆さんの学習や仕事に対して一定の参考学習価値を持ってほしいです。ありがとうございます。